* [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module
@ 2026-08-19 9:05 Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear() Zhipeng.wang_1
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Zhipeng.wang_1 @ 2026-08-19 9:05 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier, Frank Li
Cc: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Jindong Yue,
xuegang.liu, linux-kernel, imx, linux-arm-kernel
From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
This series makes the i.MX IRQSTEER driver buildable as a module.
v3 was a four-patch series: three fixes for pre-existing bugs that only
become reachable once the driver can be unbound/reloaded, followed by
the module conversion. On v3, Frank Li reviewed the unload-path fixes
and asked for three changes, all addressed here:
- The probe() error-path irq_domain leak was fixed by hand-rolling an
err_domain label. Frank suggested a devres-managed domain instead,
and to add a reusable devm_irq_domain_create_linear() helper so other
drivers benefit. Patch 1/5 adds that helper (the devres sibling of
irq_domain_create_linear()); patch 2/5 switches imx-irqsteer to it,
which fixes the leak structurally and lets remove() drop its explicit
irq_domain_remove().
- Frank questioned why remove() disposed of the mappings "twice". The
two loops disposed of two different sets: the parent output mappings
the driver created with irq_of_parse_and_map(), and the child input
mappings the domain handed out. The child mappings are freed by their
consumers and, with the domain now owned by devres, are torn down
automatically; only the parent mappings need explicit disposal. Patch
3/5 disposes of the parent mappings only, dropping the child loop.
- Frank pointed out that masking in remove() touches CHANMASK while the
device may be runtime-suspended with the clock gated. The remove()
masking is unnecessary: the next probe() quiesces the hardware before
it re-maps and unmasks the parent interrupts, which is the only window
in which a stale line could storm. Patch 4/5 therefore masks only in
probe().
Patch 5/5 then converts the driver to a module. As in v3, it lets devres
own the clock and runtime PM (devm_clk_get_enabled() +
devm_pm_runtime_set_active_enabled()).
Changes in v4:
- New patch 1/5: add devm_irq_domain_create_linear() (Frank Li).
- Patch 2/5 (was "Fix error handling path in probe()"): use the new
devm helper to manage the domain instead of hand-rolling an
err_domain label; remove() drops irq_domain_remove() (Frank Li).
- Patch 3/5 (was 2/4): dispose of the parent mappings only; the child
mapping disposal loop is removed (Frank Li).
- Patch 4/5 (was 3/4): mask interrupts in probe() only; the remove()
masking, which could touch registers while runtime-suspended, is
dropped (Frank Li).
- Patch 5/5 (was 4/4): no functional change; remove() and the probe()
error path now only dispose of the parent mappings.
v3: https://lore.kernel.org/r/20260807072346.1222389-1-Zhipeng.wang_1@oss.nxp.com
v2: https://lore.kernel.org/r/20260728092219.525449-1-Zhipeng.wang_1@oss.nxp.com
v1: https://lore.kernel.org/r/20260724090136.3595894-1-Zhipeng.wang_1@oss.nxp.com
Jindong Yue (1):
irqchip/imx-irqsteer: Allow building as module
Zhipeng Wang (4):
genirq/irqdomain: Add devm_irq_domain_create_linear()
irqchip/imx-irqsteer: Use devm to manage the IRQ domain
irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove()
irqchip/imx-irqsteer: Mask all interrupts in probe()
drivers/irqchip/Kconfig | 2 +-
drivers/irqchip/irq-imx-irqsteer.c | 52 +++++++++++++++++++-----------
include/linux/irqdomain.h | 30 +++++++++++++++++
3 files changed, 64 insertions(+), 20 deletions(-)
base-commit: bd5f485f3f026225b86573e559af0b7254ef4184
--
2.34.1
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear()
2026-08-19 9:05 [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
@ 2026-08-19 9:05 ` Zhipeng.wang_1
2026-08-19 14:23 ` Frank Li
2026-08-19 9:05 ` [PATCH v4 2/5] irqchip/imx-irqsteer: Use devm to manage the IRQ domain Zhipeng.wang_1
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Zhipeng.wang_1 @ 2026-08-19 9:05 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier, Frank Li
Cc: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Jindong Yue,
xuegang.liu, linux-kernel, imx, linux-arm-kernel
From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
irq_domain_create_linear() has no devres-managed counterpart, so every
driver that wants the domain torn down automatically on unbind has to
either open-code an irq_domain_info and call
devm_irq_domain_instantiate() directly, or register a manual devm action.
Add devm_irq_domain_create_linear() as the devres sibling of
irq_domain_create_linear(): it builds the same linear-revmap
irq_domain_info and hands it to devm_irq_domain_instantiate(), so the
domain is removed when the owning device is unbound. The return
convention matches irq_domain_create_linear() (NULL on failure) so
existing callers can switch over without changing their error checks.
Suggested-by: Frank Li <Frank.Li@nxp.com>
Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
---
include/linux/irqdomain.h | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
index 73c25d40846c..b6b360cb6525 100644
--- a/include/linux/irqdomain.h
+++ b/include/linux/irqdomain.h
@@ -457,6 +457,36 @@ static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *
return IS_ERR(d) ? NULL : d;
}
+/**
+ * devm_irq_domain_create_linear - Allocate and register a linear revmap
+ * irq_domain tied to the device lifetime.
+ * @dev: Device that owns the domain. The domain is removed via devres
+ * when the device is unbound.
+ * @fwnode: pointer to interrupt controller's FW node.
+ * @size: Number of interrupts in the domain.
+ * @ops: map/unmap domain callbacks
+ * @host_data: Controller private data pointer
+ *
+ * Returns: Newly created irq_domain, or NULL on failure.
+ */
+static inline struct irq_domain *devm_irq_domain_create_linear(struct device *dev,
+ struct fwnode_handle *fwnode,
+ unsigned int size,
+ const struct irq_domain_ops *ops,
+ void *host_data)
+{
+ const struct irq_domain_info info = {
+ .fwnode = fwnode,
+ .size = size,
+ .hwirq_max = size,
+ .ops = ops,
+ .host_data = host_data,
+ };
+ struct irq_domain *d = devm_irq_domain_instantiate(dev, &info);
+
+ return IS_ERR(d) ? NULL : d;
+}
+
static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
const struct irq_domain_ops *ops,
void *host_data)
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 2/5] irqchip/imx-irqsteer: Use devm to manage the IRQ domain
2026-08-19 9:05 [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear() Zhipeng.wang_1
@ 2026-08-19 9:05 ` Zhipeng.wang_1
2026-08-19 14:24 ` Frank Li
2026-08-19 9:05 ` [PATCH v4 3/5] irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove() Zhipeng.wang_1
` (2 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Zhipeng.wang_1 @ 2026-08-19 9:05 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier, Frank Li
Cc: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Jindong Yue,
xuegang.liu, linux-kernel, imx, linux-arm-kernel
From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
probe() creates the IRQ domain with irq_domain_create_linear() and only
tears it down on the remove() path. On the probe() error path after the
domain has been created (the fsl,num-irqs sanity check), the single
error label just calls clk_disable_unprepare() and returns, leaking the
freshly created domain. The domain-creation failure path happens to
share the same label correctly only because the domain is NULL there.
Create the domain with the new devm_irq_domain_create_linear() so the
domain is removed automatically on unbind and on any probe() failure
after it has been created. This fixes the leak on the sanity-check error
path and lets remove() drop its explicit irq_domain_remove().
Fixes: 28528fca4908 ("irqchip/imx-irqsteer: Add multi output interrupts support")
Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
---
drivers/irqchip/irq-imx-irqsteer.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
index 87b07f517be3..359b8ebc1bae 100644
--- a/drivers/irqchip/irq-imx-irqsteer.c
+++ b/drivers/irqchip/irq-imx-irqsteer.c
@@ -236,8 +236,9 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
if (irqsteer_has_chanctrl(data->devtype_data))
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
- data->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), data->reg_num * 32,
- &imx_irqsteer_domain_ops, data);
+ data->domain = devm_irq_domain_create_linear(&pdev->dev, dev_fwnode(&pdev->dev),
+ data->reg_num * 32,
+ &imx_irqsteer_domain_ops, data);
if (!data->domain) {
dev_err(&pdev->dev, "failed to create IRQ domain\n");
ret = -ENOMEM;
@@ -284,8 +285,6 @@ static void imx_irqsteer_remove(struct platform_device *pdev)
NULL, NULL);
}
- irq_domain_remove(irqsteer_data->domain);
-
clk_disable_unprepare(irqsteer_data->ipg_clk);
}
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 3/5] irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove()
2026-08-19 9:05 [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear() Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 2/5] irqchip/imx-irqsteer: Use devm to manage the IRQ domain Zhipeng.wang_1
@ 2026-08-19 9:05 ` Zhipeng.wang_1
2026-08-19 14:25 ` Frank Li
2026-08-19 9:05 ` [PATCH v4 4/5] irqchip/imx-irqsteer: Mask all interrupts in probe() Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 5/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
4 siblings, 1 reply; 10+ messages in thread
From: Zhipeng.wang_1 @ 2026-08-19 9:05 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier, Frank Li
Cc: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Jindong Yue,
xuegang.liu, linux-kernel, imx, linux-arm-kernel
From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
probe() maps the parent output interrupts with irq_of_parse_and_map(),
but remove() only unchains the handlers and never disposes of those
mappings, leaking them on unbind. The child mappings handed out by the
domain are freed by their consumers and, together with the domain, are
now torn down by devres, so remove() only has to dispose of the parent
mappings it created itself.
Dispose of the parent mappings alongside the chained-handler teardown.
Fixes: 0136afa08967 ("irqchip: Add driver for imx-irqsteer controller")
Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
---
drivers/irqchip/irq-imx-irqsteer.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
index 359b8ebc1bae..320082d3a632 100644
--- a/drivers/irqchip/irq-imx-irqsteer.c
+++ b/drivers/irqchip/irq-imx-irqsteer.c
@@ -283,6 +283,7 @@ static void imx_irqsteer_remove(struct platform_device *pdev)
irq_set_chained_handler_and_data(irqsteer_data->irq[i],
NULL, NULL);
+ irq_dispose_mapping(irqsteer_data->irq[i]);
}
clk_disable_unprepare(irqsteer_data->ipg_clk);
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 4/5] irqchip/imx-irqsteer: Mask all interrupts in probe()
2026-08-19 9:05 [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
` (2 preceding siblings ...)
2026-08-19 9:05 ` [PATCH v4 3/5] irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove() Zhipeng.wang_1
@ 2026-08-19 9:05 ` Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 5/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
4 siblings, 0 replies; 10+ messages in thread
From: Zhipeng.wang_1 @ 2026-08-19 9:05 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier, Frank Li
Cc: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Jindong Yue,
xuegang.liu, linux-kernel, imx, linux-arm-kernel
From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
probe() sets up the chained handlers without first masking the input
interrupts. For a built-in driver this happened to be harmless because
CHANMASK resets to all-masked, but once the driver can be unloaded and
reloaded a child interrupt left unmasked at unload time survives in
hardware. On the next probe() the parent interrupts are re-mapped and
unmasked before the new domain is ready, so a still-asserted line
immediately storms the parent with no handler to service it.
Mask all interrupts in probe() before wiring up the chained handlers.
CHANMASK uses inverted polarity (a set bit enables the interrupt), so
masking means writing zero. This mirrors the sibling NXP chained mux
irq-imx-intmux.c, which masks all sources at probe() time.
Masking is only done in probe(), not in remove(): the next probe()
quiesces the hardware before it re-maps and unmasks the parent
interrupts, which is the only window in which a stale line could storm.
Masking in remove() would also mean touching CHANMASK while the device
may already be runtime-suspended with the clock gated.
Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
---
drivers/irqchip/irq-imx-irqsteer.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
index 320082d3a632..a9909ecb6fef 100644
--- a/drivers/irqchip/irq-imx-irqsteer.c
+++ b/drivers/irqchip/irq-imx-irqsteer.c
@@ -236,6 +236,14 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
if (irqsteer_has_chanctrl(data->devtype_data))
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
+ /*
+ * Mask all interrupts before wiring up the chained handlers. CHANMASK
+ * has inverted polarity (a set bit enables the interrupt), so writing
+ * zero masks the source.
+ */
+ for (i = 0; i < data->reg_num; i++)
+ writel_relaxed(0, data->regs + CHANMASK(i, data->reg_num));
+
data->domain = devm_irq_domain_create_linear(&pdev->dev, dev_fwnode(&pdev->dev),
data->reg_num * 32,
&imx_irqsteer_domain_ops, data);
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v4 5/5] irqchip/imx-irqsteer: Allow building as module
2026-08-19 9:05 [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
` (3 preceding siblings ...)
2026-08-19 9:05 ` [PATCH v4 4/5] irqchip/imx-irqsteer: Mask all interrupts in probe() Zhipeng.wang_1
@ 2026-08-19 9:05 ` Zhipeng.wang_1
2026-08-19 15:17 ` Frank Li
4 siblings, 1 reply; 10+ messages in thread
From: Zhipeng.wang_1 @ 2026-08-19 9:05 UTC (permalink / raw)
To: Thomas Gleixner, Marc Zyngier, Frank Li
Cc: Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Jindong Yue,
xuegang.liu, linux-kernel, imx, linux-arm-kernel
From: Jindong Yue <jindong.yue@nxp.com>
Make the driver buildable as a module by turning the Kconfig symbol into
a tristate and using module_platform_driver() instead of
builtin_platform_driver().
Now that the driver can be unloaded and reloaded, let the driver core
own the clock and runtime PM lifetime so that the probe() error path and
remove() do not have to hand-balance them:
- acquire the clock with devm_clk_get_enabled() instead of a bare
devm_clk_get() followed by a manual clk_prepare_enable(), so it is
prepared/enabled for the device lifetime and released on unbind;
- keep only clk_enable()/clk_disable() in the runtime PM callbacks,
since prepare/unprepare is now handled once by devres;
- enable runtime PM with devm_pm_runtime_set_active_enabled(), which
marks the device active (matching the enabled clock) and disables
runtime PM on unbind.
With the clock, runtime PM and IRQ domain all owned by devres, remove()
and the probe() error path only have to dispose of the parent IRQ
mappings.
Signed-off-by: Jindong Yue <jindong.yue@nxp.com>
Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
---
drivers/irqchip/Kconfig | 2 +-
drivers/irqchip/irq-imx-irqsteer.c | 36 +++++++++++++++++-------------
2 files changed, 22 insertions(+), 16 deletions(-)
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 20b77fbc51ee..105108d2e6ff 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -555,7 +555,7 @@ config CSKY_APB_INTC
the controller's register.
config IMX_IRQSTEER
- bool "i.MX IRQSTEER support"
+ tristate "i.MX IRQSTEER support"
depends on ARCH_MXC || ARCH_S32 || COMPILE_TEST
default y if ARCH_MXC || ARCH_S32
select IRQ_DOMAIN
diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
index a9909ecb6fef..a60cc527e619 100644
--- a/drivers/irqchip/irq-imx-irqsteer.c
+++ b/drivers/irqchip/irq-imx-irqsteer.c
@@ -10,6 +10,7 @@
#include <linux/irqchip/chained_irq.h>
#include <linux/irqdomain.h>
#include <linux/kernel.h>
+#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
@@ -193,7 +194,7 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
return PTR_ERR(data->regs);
}
- data->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
+ data->ipg_clk = devm_clk_get_enabled(&pdev->dev, "ipg");
if (IS_ERR(data->ipg_clk))
return dev_err_probe(&pdev->dev, PTR_ERR(data->ipg_clk),
"failed to get ipg clk\n");
@@ -226,12 +227,6 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
return -ENOMEM;
}
- ret = clk_prepare_enable(data->ipg_clk);
- if (ret) {
- dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret);
- return ret;
- }
-
/* steer all IRQs into configured channel */
if (irqsteer_has_chanctrl(data->devtype_data))
writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
@@ -271,12 +266,21 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data);
- pm_runtime_set_active(&pdev->dev);
- pm_runtime_enable(&pdev->dev);
+ ret = devm_pm_runtime_set_active_enabled(&pdev->dev);
+ if (ret)
+ goto err_irq;
return 0;
+
+err_irq:
+ for (i = 0; i < data->irq_count; i++) {
+ if (!data->irq[i])
+ break;
+
+ irq_set_chained_handler_and_data(data->irq[i], NULL, NULL);
+ irq_dispose_mapping(data->irq[i]);
+ }
out:
- clk_disable_unprepare(data->ipg_clk);
return ret;
}
@@ -293,8 +297,6 @@ static void imx_irqsteer_remove(struct platform_device *pdev)
NULL, NULL);
irq_dispose_mapping(irqsteer_data->irq[i]);
}
-
- clk_disable_unprepare(irqsteer_data->ipg_clk);
}
#ifdef CONFIG_PM
@@ -324,7 +326,7 @@ static int imx_irqsteer_suspend(struct device *dev)
struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev);
imx_irqsteer_save_regs(irqsteer_data);
- clk_disable_unprepare(irqsteer_data->ipg_clk);
+ clk_disable(irqsteer_data->ipg_clk);
return 0;
}
@@ -334,7 +336,7 @@ static int imx_irqsteer_resume(struct device *dev)
struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev);
int ret;
- ret = clk_prepare_enable(irqsteer_data->ipg_clk);
+ ret = clk_enable(irqsteer_data->ipg_clk);
if (ret) {
dev_err(dev, "failed to enable ipg clk: %d\n", ret);
return ret;
@@ -357,6 +359,7 @@ static const struct of_device_id imx_irqsteer_dt_ids[] = {
{ .compatible = "nxp,s32n79-irqsteer", .data = &s32n79_data },
{},
};
+MODULE_DEVICE_TABLE(of, imx_irqsteer_dt_ids);
static struct platform_driver imx_irqsteer_driver = {
.driver = {
@@ -367,4 +370,7 @@ static struct platform_driver imx_irqsteer_driver = {
.probe = imx_irqsteer_probe,
.remove = imx_irqsteer_remove,
};
-builtin_platform_driver(imx_irqsteer_driver);
+module_platform_driver(imx_irqsteer_driver);
+
+MODULE_DESCRIPTION("i.MX IRQSTEER interrupt multiplexer/remapper driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear()
2026-08-19 9:05 ` [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear() Zhipeng.wang_1
@ 2026-08-19 14:23 ` Frank Li
0 siblings, 0 replies; 10+ messages in thread
From: Frank Li @ 2026-08-19 14:23 UTC (permalink / raw)
To: Zhipeng.wang_1
Cc: Thomas Gleixner, Marc Zyngier, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jindong Yue, xuegang.liu,
linux-kernel, imx, linux-arm-kernel
On Wed, Aug 19, 2026 at 06:05:39PM +0900, Zhipeng.wang_1@oss.nxp.com wrote:
> From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
>
> irq_domain_create_linear() has no devres-managed counterpart, so every
> driver that wants the domain torn down automatically on unbind has to
> either open-code an irq_domain_info and call
> devm_irq_domain_instantiate() directly, or register a manual devm action.
>
> Add devm_irq_domain_create_linear() as the devres sibling of
> irq_domain_create_linear(): it builds the same linear-revmap
> irq_domain_info and hands it to devm_irq_domain_instantiate(), so the
> domain is removed when the owning device is unbound. The return
> convention matches irq_domain_create_linear() (NULL on failure) so
> existing callers can switch over without changing their error checks.
>
> Suggested-by: Frank Li <Frank.Li@nxp.com>
> Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> include/linux/irqdomain.h | 30 ++++++++++++++++++++++++++++++
> 1 file changed, 30 insertions(+)
>
> diff --git a/include/linux/irqdomain.h b/include/linux/irqdomain.h
> index 73c25d40846c..b6b360cb6525 100644
> --- a/include/linux/irqdomain.h
> +++ b/include/linux/irqdomain.h
> @@ -457,6 +457,36 @@ static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *
> return IS_ERR(d) ? NULL : d;
> }
>
> +/**
> + * devm_irq_domain_create_linear - Allocate and register a linear revmap
> + * irq_domain tied to the device lifetime.
> + * @dev: Device that owns the domain. The domain is removed via devres
> + * when the device is unbound.
> + * @fwnode: pointer to interrupt controller's FW node.
> + * @size: Number of interrupts in the domain.
> + * @ops: map/unmap domain callbacks
> + * @host_data: Controller private data pointer
> + *
> + * Returns: Newly created irq_domain, or NULL on failure.
> + */
> +static inline struct irq_domain *devm_irq_domain_create_linear(struct device *dev,
> + struct fwnode_handle *fwnode,
> + unsigned int size,
> + const struct irq_domain_ops *ops,
> + void *host_data)
> +{
> + const struct irq_domain_info info = {
> + .fwnode = fwnode,
> + .size = size,
> + .hwirq_max = size,
> + .ops = ops,
> + .host_data = host_data,
> + };
> + struct irq_domain *d = devm_irq_domain_instantiate(dev, &info);
> +
> + return IS_ERR(d) ? NULL : d;
> +}
> +
> static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
> const struct irq_domain_ops *ops,
> void *host_data)
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 2/5] irqchip/imx-irqsteer: Use devm to manage the IRQ domain
2026-08-19 9:05 ` [PATCH v4 2/5] irqchip/imx-irqsteer: Use devm to manage the IRQ domain Zhipeng.wang_1
@ 2026-08-19 14:24 ` Frank Li
0 siblings, 0 replies; 10+ messages in thread
From: Frank Li @ 2026-08-19 14:24 UTC (permalink / raw)
To: Zhipeng.wang_1
Cc: Thomas Gleixner, Marc Zyngier, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jindong Yue, xuegang.liu,
linux-kernel, imx, linux-arm-kernel
On Wed, Aug 19, 2026 at 06:05:40PM +0900, Zhipeng.wang_1@oss.nxp.com wrote:
> From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
>
> probe() creates the IRQ domain with irq_domain_create_linear() and only
> tears it down on the remove() path. On the probe() error path after the
> domain has been created (the fsl,num-irqs sanity check), the single
> error label just calls clk_disable_unprepare() and returns, leaking the
> freshly created domain. The domain-creation failure path happens to
> share the same label correctly only because the domain is NULL there.
>
> Create the domain with the new devm_irq_domain_create_linear() so the
> domain is removed automatically on unbind and on any probe() failure
> after it has been created. This fixes the leak on the sanity-check error
> path and lets remove() drop its explicit irq_domain_remove().
>
> Fixes: 28528fca4908 ("irqchip/imx-irqsteer: Add multi output interrupts support")
Nit: First patch also need fixes tags to backport.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
> ---
> drivers/irqchip/irq-imx-irqsteer.c | 7 +++----
> 1 file changed, 3 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
> index 87b07f517be3..359b8ebc1bae 100644
> --- a/drivers/irqchip/irq-imx-irqsteer.c
> +++ b/drivers/irqchip/irq-imx-irqsteer.c
> @@ -236,8 +236,9 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
> if (irqsteer_has_chanctrl(data->devtype_data))
> writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
>
> - data->domain = irq_domain_create_linear(dev_fwnode(&pdev->dev), data->reg_num * 32,
> - &imx_irqsteer_domain_ops, data);
> + data->domain = devm_irq_domain_create_linear(&pdev->dev, dev_fwnode(&pdev->dev),
> + data->reg_num * 32,
> + &imx_irqsteer_domain_ops, data);
> if (!data->domain) {
> dev_err(&pdev->dev, "failed to create IRQ domain\n");
> ret = -ENOMEM;
> @@ -284,8 +285,6 @@ static void imx_irqsteer_remove(struct platform_device *pdev)
> NULL, NULL);
> }
>
> - irq_domain_remove(irqsteer_data->domain);
> -
> clk_disable_unprepare(irqsteer_data->ipg_clk);
> }
>
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 3/5] irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove()
2026-08-19 9:05 ` [PATCH v4 3/5] irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove() Zhipeng.wang_1
@ 2026-08-19 14:25 ` Frank Li
0 siblings, 0 replies; 10+ messages in thread
From: Frank Li @ 2026-08-19 14:25 UTC (permalink / raw)
To: Zhipeng.wang_1
Cc: Thomas Gleixner, Marc Zyngier, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jindong Yue, xuegang.liu,
linux-kernel, imx, linux-arm-kernel
On Wed, Aug 19, 2026 at 06:05:41PM +0900, Zhipeng.wang_1@oss.nxp.com wrote:
> From: Zhipeng Wang <zhipeng.wang_1@nxp.com>
>
> probe() maps the parent output interrupts with irq_of_parse_and_map(),
> but remove() only unchains the handlers and never disposes of those
> mappings, leaking them on unbind. The child mappings handed out by the
> domain are freed by their consumers and, together with the domain, are
> now torn down by devres, so remove() only has to dispose of the parent
> mappings it created itself.
>
> Dispose of the parent mappings alongside the chained-handler teardown.
>
> Fixes: 0136afa08967 ("irqchip: Add driver for imx-irqsteer controller")
> Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/irqchip/irq-imx-irqsteer.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
> index 359b8ebc1bae..320082d3a632 100644
> --- a/drivers/irqchip/irq-imx-irqsteer.c
> +++ b/drivers/irqchip/irq-imx-irqsteer.c
> @@ -283,6 +283,7 @@ static void imx_irqsteer_remove(struct platform_device *pdev)
>
> irq_set_chained_handler_and_data(irqsteer_data->irq[i],
> NULL, NULL);
> + irq_dispose_mapping(irqsteer_data->irq[i]);
> }
>
> clk_disable_unprepare(irqsteer_data->ipg_clk);
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 5/5] irqchip/imx-irqsteer: Allow building as module
2026-08-19 9:05 ` [PATCH v4 5/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
@ 2026-08-19 15:17 ` Frank Li
0 siblings, 0 replies; 10+ messages in thread
From: Frank Li @ 2026-08-19 15:17 UTC (permalink / raw)
To: Zhipeng.wang_1
Cc: Thomas Gleixner, Marc Zyngier, Frank Li, Sascha Hauer,
Pengutronix Kernel Team, Fabio Estevam, Jindong Yue, xuegang.liu,
linux-kernel, imx, linux-arm-kernel
On Wed, Aug 19, 2026 at 06:05:43PM +0900, Zhipeng.wang_1@oss.nxp.com wrote:
> From: Jindong Yue <jindong.yue@nxp.com>
>
> Make the driver buildable as a module by turning the Kconfig symbol into
> a tristate and using module_platform_driver() instead of
> builtin_platform_driver().
>
> Now that the driver can be unloaded and reloaded, let the driver core
> own the clock and runtime PM lifetime so that the probe() error path and
> remove() do not have to hand-balance them:
>
> - acquire the clock with devm_clk_get_enabled() instead of a bare
> devm_clk_get() followed by a manual clk_prepare_enable(), so it is
> prepared/enabled for the device lifetime and released on unbind;
> - keep only clk_enable()/clk_disable() in the runtime PM callbacks,
> since prepare/unprepare is now handled once by devres;
devm_clk_get_enable() will do prepare() and enable(). tear down also do
unprepare() and disable()
To avoid both devm_clk() and runtime pm suspend both unprepare and
disable() clock to make wrong clock refererence.
need in remove function
/*
* Resume the device so runtime_resume() re-enables the clock.
* devm cleanup (clk_disable_unprepare) runs after .remove() returns,
* so the clock will be enabled and the disable is safe.
*/
pm_runtime_resume_and_get(dev);
/* devm_clk_get_enable and devm_pm_runtime_enable clean up automatically */
Frank
> - enable runtime PM with devm_pm_runtime_set_active_enabled(), which
> marks the device active (matching the enabled clock) and disables
> runtime PM on unbind.
>
> With the clock, runtime PM and IRQ domain all owned by devres, remove()
> and the probe() error path only have to dispose of the parent IRQ
> mappings.
>
> Signed-off-by: Jindong Yue <jindong.yue@nxp.com>
> Signed-off-by: Zhipeng Wang <zhipeng.wang_1@nxp.com>
> ---
> drivers/irqchip/Kconfig | 2 +-
> drivers/irqchip/irq-imx-irqsteer.c | 36 +++++++++++++++++-------------
> 2 files changed, 22 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
> index 20b77fbc51ee..105108d2e6ff 100644
> --- a/drivers/irqchip/Kconfig
> +++ b/drivers/irqchip/Kconfig
> @@ -555,7 +555,7 @@ config CSKY_APB_INTC
> the controller's register.
>
> config IMX_IRQSTEER
> - bool "i.MX IRQSTEER support"
> + tristate "i.MX IRQSTEER support"
> depends on ARCH_MXC || ARCH_S32 || COMPILE_TEST
> default y if ARCH_MXC || ARCH_S32
> select IRQ_DOMAIN
> diff --git a/drivers/irqchip/irq-imx-irqsteer.c b/drivers/irqchip/irq-imx-irqsteer.c
> index a9909ecb6fef..a60cc527e619 100644
> --- a/drivers/irqchip/irq-imx-irqsteer.c
> +++ b/drivers/irqchip/irq-imx-irqsteer.c
> @@ -10,6 +10,7 @@
> #include <linux/irqchip/chained_irq.h>
> #include <linux/irqdomain.h>
> #include <linux/kernel.h>
> +#include <linux/module.h>
> #include <linux/of.h>
> #include <linux/of_irq.h>
> #include <linux/platform_device.h>
> @@ -193,7 +194,7 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
> return PTR_ERR(data->regs);
> }
>
> - data->ipg_clk = devm_clk_get(&pdev->dev, "ipg");
> + data->ipg_clk = devm_clk_get_enabled(&pdev->dev, "ipg");
> if (IS_ERR(data->ipg_clk))
> return dev_err_probe(&pdev->dev, PTR_ERR(data->ipg_clk),
> "failed to get ipg clk\n");
> @@ -226,12 +227,6 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
> return -ENOMEM;
> }
>
> - ret = clk_prepare_enable(data->ipg_clk);
> - if (ret) {
> - dev_err(&pdev->dev, "failed to enable ipg clk: %d\n", ret);
> - return ret;
> - }
> -
> /* steer all IRQs into configured channel */
> if (irqsteer_has_chanctrl(data->devtype_data))
> writel_relaxed(BIT(data->channel), data->regs + CHANCTRL);
> @@ -271,12 +266,21 @@ static int imx_irqsteer_probe(struct platform_device *pdev)
>
> platform_set_drvdata(pdev, data);
>
> - pm_runtime_set_active(&pdev->dev);
> - pm_runtime_enable(&pdev->dev);
> + ret = devm_pm_runtime_set_active_enabled(&pdev->dev);
> + if (ret)
> + goto err_irq;
>
> return 0;
> +
> +err_irq:
> + for (i = 0; i < data->irq_count; i++) {
> + if (!data->irq[i])
> + break;
> +
> + irq_set_chained_handler_and_data(data->irq[i], NULL, NULL);
> + irq_dispose_mapping(data->irq[i]);
> + }
> out:
> - clk_disable_unprepare(data->ipg_clk);
> return ret;
> }
>
> @@ -293,8 +297,6 @@ static void imx_irqsteer_remove(struct platform_device *pdev)
> NULL, NULL);
> irq_dispose_mapping(irqsteer_data->irq[i]);
> }
> -
> - clk_disable_unprepare(irqsteer_data->ipg_clk);
> }
>
> #ifdef CONFIG_PM
> @@ -324,7 +326,7 @@ static int imx_irqsteer_suspend(struct device *dev)
> struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev);
>
> imx_irqsteer_save_regs(irqsteer_data);
> - clk_disable_unprepare(irqsteer_data->ipg_clk);
> + clk_disable(irqsteer_data->ipg_clk);
>
> return 0;
> }
> @@ -334,7 +336,7 @@ static int imx_irqsteer_resume(struct device *dev)
> struct irqsteer_data *irqsteer_data = dev_get_drvdata(dev);
> int ret;
>
> - ret = clk_prepare_enable(irqsteer_data->ipg_clk);
> + ret = clk_enable(irqsteer_data->ipg_clk);
> if (ret) {
> dev_err(dev, "failed to enable ipg clk: %d\n", ret);
> return ret;
> @@ -357,6 +359,7 @@ static const struct of_device_id imx_irqsteer_dt_ids[] = {
> { .compatible = "nxp,s32n79-irqsteer", .data = &s32n79_data },
> {},
> };
> +MODULE_DEVICE_TABLE(of, imx_irqsteer_dt_ids);
>
> static struct platform_driver imx_irqsteer_driver = {
> .driver = {
> @@ -367,4 +370,7 @@ static struct platform_driver imx_irqsteer_driver = {
> .probe = imx_irqsteer_probe,
> .remove = imx_irqsteer_remove,
> };
> -builtin_platform_driver(imx_irqsteer_driver);
> +module_platform_driver(imx_irqsteer_driver);
> +
> +MODULE_DESCRIPTION("i.MX IRQSTEER interrupt multiplexer/remapper driver");
> +MODULE_LICENSE("GPL");
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-08-19 15:17 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-19 9:05 [PATCH v4 0/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 1/5] genirq/irqdomain: Add devm_irq_domain_create_linear() Zhipeng.wang_1
2026-08-19 14:23 ` Frank Li
2026-08-19 9:05 ` [PATCH v4 2/5] irqchip/imx-irqsteer: Use devm to manage the IRQ domain Zhipeng.wang_1
2026-08-19 14:24 ` Frank Li
2026-08-19 9:05 ` [PATCH v4 3/5] irqchip/imx-irqsteer: Dispose of parent IRQ mappings in remove() Zhipeng.wang_1
2026-08-19 14:25 ` Frank Li
2026-08-19 9:05 ` [PATCH v4 4/5] irqchip/imx-irqsteer: Mask all interrupts in probe() Zhipeng.wang_1
2026-08-19 9:05 ` [PATCH v4 5/5] irqchip/imx-irqsteer: Allow building as module Zhipeng.wang_1
2026-08-19 15:17 ` Frank Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox