From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT)
that allows software to explicitly assert interrupts toward individual
CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding
interrupt.
Introduce a debug mechanism to trigger software interrupts on individual
Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind
CONFIG_DEBUG_FS and a module parameter to ensure it only exists when
explicitly enabled.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
drivers/irqchip/irq-renesas-rzv2h.c | 111 ++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
diff --git a/drivers/irqchip/irq-renesas-rzv2h.c b/drivers/irqchip/irq-renesas-rzv2h.c
index 4aa772ba1a1f..7d3ce1d762f0 100644
--- a/drivers/irqchip/irq-renesas-rzv2h.c
+++ b/drivers/irqchip/irq-renesas-rzv2h.c
@@ -11,16 +11,23 @@
#include <linux/bitfield.h>
#include <linux/cleanup.h>
+#include <linux/cpu.h>
+#include <linux/debugfs.h>
#include <linux/err.h>
+#include <linux/fs.h>
#include <linux/io.h>
#include <linux/irqchip.h>
#include <linux/irqchip/irq-renesas-rzv2h.h>
#include <linux/irqdomain.h>
+#include <linux/kconfig.h>
+#include <linux/kstrtox.h>
+#include <linux/moduleparam.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/spinlock.h>
#include <linux/syscore_ops.h>
+#include <linux/uaccess.h>
/* DT "interrupts" indexes */
#define ICU_IRQ_START 1
@@ -40,6 +47,7 @@
#define ICU_TSCLR 0x24
#define ICU_TITSR(k) (0x28 + (k) * 4)
#define ICU_TSSR(k) (0x30 + (k) * 4)
+#define ICU_SWINT 0x130
#define ICU_DMkSELy(k, y) (0x420 + (k) * 0x20 + (y) * 4)
#define ICU_DMACKSELk(k) (0x500 + (k) * 4)
@@ -90,6 +98,13 @@
#define ICU_RZG3E_TSSEL_MAX_VAL 0x8c
#define ICU_RZV2H_TSSEL_MAX_VAL 0x55
+#define ICU_SWINT_NUM 4
+
+static bool enable_icu_debug;
+module_param_named(debug, enable_icu_debug, bool, 0644);
+MODULE_PARM_DESC(debug,
+ "Enable RZ/V2H ICU debug/diagnostic interrupts (default: false)");
+
/**
* struct rzv2h_irqc_reg_cache - registers cache (necessary for suspend/resume)
* @nitsr: ICU_NITSR register
@@ -550,6 +565,98 @@ static int rzv2h_icu_parse_interrupts(struct rzv2h_icu_priv *priv, struct device
return 0;
}
+static irqreturn_t rzv2h_icu_swint_irq(int irq, void *data)
+{
+ u8 cpu = *(u8 *)data;
+
+ pr_debug("SWINT interrupt for CA55 core %u\n", cpu);
+ return IRQ_HANDLED;
+}
+
+static void rzv2h_icu_remove_debugfs(void *file)
+{
+ debugfs_remove(file);
+}
+
+static ssize_t rzv2h_icu_swint_write(struct file *file, const char __user *ubuf,
+ size_t len, loff_t *ppos)
+{
+ struct rzv2h_icu_priv *priv = file->private_data;
+ unsigned long cpu;
+ char buf[32];
+ int ret;
+
+ len = min(len, sizeof(buf) - 1);
+ if (copy_from_user(buf, ubuf, len))
+ return -EFAULT;
+ buf[len] = '\0';
+
+ ret = kstrtoul(strim(buf), 0, &cpu);
+ if (ret)
+ return ret;
+
+ if (cpu >= ICU_SWINT_NUM || cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ if (!cpu_online(cpu))
+ return -ENODEV;
+
+ writel(BIT(cpu), priv->base + ICU_SWINT);
+ return len;
+}
+
+static const struct file_operations rzv2h_icu_swint_fops = {
+ .open = simple_open,
+ .write = rzv2h_icu_swint_write,
+ .llseek = noop_llseek,
+};
+
+static int rzv2h_icu_setup_debug_irqs(struct platform_device *pdev)
+{
+ static const u8 swint_idx[ICU_SWINT_NUM] = { 0, 1, 2, 3 };
+ static const char * const rzv2h_swint_names[] = {
+ "int-ca55-0", "int-ca55-1",
+ "int-ca55-2", "int-ca55-3",
+ };
+ struct device *dev = &pdev->dev;
+ struct dentry *dentry;
+ struct dentry *dir;
+ unsigned int i;
+ int icu_irq;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_DEBUG_FS) || !enable_icu_debug)
+ return 0;
+
+ dev_info(dev, "RZ/V2H ICU debug interrupts enabled\n");
+
+ for (i = 0; i < ICU_SWINT_NUM; i++) {
+ icu_irq = platform_get_irq_byname(pdev, rzv2h_swint_names[i]);
+ if (icu_irq < 0)
+ return dev_err_probe(dev, icu_irq,
+ "Failed to get %s IRQ\n", rzv2h_swint_names[i]);
+ ret = devm_request_irq(dev, icu_irq, rzv2h_icu_swint_irq, 0, dev_name(dev),
+ (void *)&swint_idx[i]);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request SWINT IRQ: %s\n",
+ rzv2h_swint_names[i]);
+ }
+
+ dir = debugfs_create_dir("rzv2h_icu", NULL);
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+
+ ret = devm_add_action_or_reset(dev, rzv2h_icu_remove_debugfs, dir);
+ if (ret)
+ return ret;
+
+ dentry = debugfs_create_file("swint", 0200, dir, rzv2h_icu_data, &rzv2h_icu_swint_fops);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+
+ return devm_add_action_or_reset(dev, rzv2h_icu_remove_debugfs, dentry);
+}
+
static int rzv2h_icu_probe_common(struct platform_device *pdev, struct device_node *parent,
const struct rzv2h_hw_info *hw_info)
{
@@ -605,6 +712,10 @@ static int rzv2h_icu_probe_common(struct platform_device *pdev, struct device_no
register_syscore(&rzv2h_irqc_syscore);
+ ret = rzv2h_icu_setup_debug_irqs(pdev);
+ if (ret)
+ goto pm_put;
+
/*
* coccicheck complains about a missing put_device call before returning, but it's a false
* positive. We still need dev after successfully returning from this function.
--
2.52.0
On Wed, Jan 21 2026 at 15:01, Prabhakar wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>
> The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT)
> that allows software to explicitly assert interrupts toward individual
> CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding
> interrupt.
>
> Introduce a debug mechanism to trigger software interrupts on individual
> Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind
> CONFIG_DEBUG_FS and a module parameter to ensure it only exists when
> explicitly enabled.
Can't you reuse/extend the existing mechanism provided by
CONFIG_GENERIC_IRQ_INJECTION (irq_inject_interrupt(), irq_debug_write())
instead of implementing yet another ad hoc debugfs magic?
Thanks,
tglx
Hi Thomas, Thank you for the feedback. On Mon, Jan 26, 2026 at 4:03 PM Thomas Gleixner <tglx@kernel.org> wrote: > > On Wed, Jan 21 2026 at 15:01, Prabhakar wrote: > > From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com> > > > > The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT) > > that allows software to explicitly assert interrupts toward individual > > CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding > > interrupt. > > > > Introduce a debug mechanism to trigger software interrupts on individual > > Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind > > CONFIG_DEBUG_FS and a module parameter to ensure it only exists when > > explicitly enabled. > > Can't you reuse/extend the existing mechanism provided by > CONFIG_GENERIC_IRQ_INJECTION (irq_inject_interrupt(), irq_debug_write()) > instead of implementing yet another ad hoc debugfs magic? > Can you please point me to a driver which makes use of it? In my case the interrupt needs to be triggered when BIT(n) (n=0-3) is written to ICU_SWINT. Cheers, Prabhakar
On Thu, Jan 29 2026 at 21:24, Prabhakar Lad wrote:
> On Mon, Jan 26, 2026 at 4:03 PM Thomas Gleixner <tglx@kernel.org> wrote:
>>
>> On Wed, Jan 21 2026 at 15:01, Prabhakar wrote:
>> > From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>> >
>> > The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT)
>> > that allows software to explicitly assert interrupts toward individual
>> > CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding
>> > interrupt.
>> >
>> > Introduce a debug mechanism to trigger software interrupts on individual
>> > Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind
>> > CONFIG_DEBUG_FS and a module parameter to ensure it only exists when
>> > explicitly enabled.
>>
>> Can't you reuse/extend the existing mechanism provided by
>> CONFIG_GENERIC_IRQ_INJECTION (irq_inject_interrupt(), irq_debug_write())
>> instead of implementing yet another ad hoc debugfs magic?
>>
> Can you please point me to a driver which makes use of it? In my case
> the interrupt needs to be triggered when BIT(n) (n=0-3) is written to
> ICU_SWINT.
Care to look what irq_inject_interrupt() does?
It tries first to inject the interrupt via irq_set_irqchip_state(),
which only works when a chip in the hierarchy implements the
chip::irq_set_irqchip_state() callback.
If that fails, it uses the resend mechanism, which utilizes the
chip::irq_retrigger() callback.
I'm sure you know how to grep for drivers which implement one of them :)
Thanks,
tglx
Hi Thomas,
On Thu, Jan 29, 2026 at 9:59 PM Thomas Gleixner <tglx@kernel.org> wrote:
>
> On Thu, Jan 29 2026 at 21:24, Prabhakar Lad wrote:
> > On Mon, Jan 26, 2026 at 4:03 PM Thomas Gleixner <tglx@kernel.org> wrote:
> >>
> >> On Wed, Jan 21 2026 at 15:01, Prabhakar wrote:
> >> > From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> >> >
> >> > The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT)
> >> > that allows software to explicitly assert interrupts toward individual
> >> > CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding
> >> > interrupt.
> >> >
> >> > Introduce a debug mechanism to trigger software interrupts on individual
> >> > Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind
> >> > CONFIG_DEBUG_FS and a module parameter to ensure it only exists when
> >> > explicitly enabled.
> >>
> >> Can't you reuse/extend the existing mechanism provided by
> >> CONFIG_GENERIC_IRQ_INJECTION (irq_inject_interrupt(), irq_debug_write())
> >> instead of implementing yet another ad hoc debugfs magic?
> >>
> > Can you please point me to a driver which makes use of it? In my case
> > the interrupt needs to be triggered when BIT(n) (n=0-3) is written to
> > ICU_SWINT.
>
> Care to look what irq_inject_interrupt() does?
>
> It tries first to inject the interrupt via irq_set_irqchip_state(),
> which only works when a chip in the hierarchy implements the
> chip::irq_set_irqchip_state() callback.
>
I did implement irq_set_irqchip_state but it doesn't land in the
rzv2h_icu_irq_set_irqchip_state(). So I was wondering if I missed
something.
#Trigger int-ca55-0
root@rzv2h-evk:/sys/kernel/debug/irq/irqs# echo trigger > 14
#The trace looks like below:
irq_debug_write()
-> irq_inject_interrupt()
-> irq_set_irqchip_state()
This lands in GICV3. For the RZ/V2H ICU only interrupts port_irqx and
tintx interrupts are registered in irq_domain_create_hierarchy() for
the rest of the interrupts these are supposed to be directly handled
by GICv3.
root@rzv2h-evk:/sys/kernel/debug/irq/irqs# cat /proc/interrupts | grep
interr | grep 294
14: 1 0 0 0 GICv3 294 Edge
10400000.interrupt-controller
root@rzv2h-evk:/sys/kernel/debug/irq/irqs# cat 14
handler: handle_fasteoi_irq
device: (null)
status: 0x00000001
istate: 0x00004000
ddepth: 0
wdepth: 0
dstate: 0x0b400201
IRQ_TYPE_EDGE_RISING
IRQD_ACTIVATED
IRQD_IRQ_STARTED
IRQD_SINGLE_TARGET
IRQD_DEFAULT_TRIGGER_SET
IRQD_HANDLE_ENFORCE_IRQCTX
node: -1
affinity: 0-3
effectiv: 0
domain: :soc:interrupt-controller@14900000-1
hwirq: 0x126
chip: GICv3
flags: 0x15
IRQCHIP_SET_TYPE_MASKED
IRQCHIP_MASK_ON_SUSPEND
IRQCHIP_SKIP_SET_WAKE
How do you propose to handle this? irq_inject_interrupt() would work
if I move int-ca55-x and icu-error-ca55 under
irq_domain_create_hierarchy().
Cheers,
Prabhakar
On Fri, Jan 30 2026 at 11:17, Lad, Prabhakar wrote: > On Thu, Jan 29, 2026 at 9:59 PM Thomas Gleixner <tglx@kernel.org> wrote: >> It tries first to inject the interrupt via irq_set_irqchip_state(), >> which only works when a chip in the hierarchy implements the >> chip::irq_set_irqchip_state() callback. >> > I did implement irq_set_irqchip_state but it doesn't land in the > rzv2h_icu_irq_set_irqchip_state(). So I was wondering if I missed > something. > > #Trigger int-ca55-0 > root@rzv2h-evk:/sys/kernel/debug/irq/irqs# echo trigger > 14 > > #The trace looks like below: > irq_debug_write() > -> irq_inject_interrupt() > -> irq_set_irqchip_state() > > This lands in GICV3. For the RZ/V2H ICU only interrupts port_irqx and > tintx interrupts are registered in irq_domain_create_hierarchy() for > the rest of the interrupts these are supposed to be directly handled > by GICv3. ... > How do you propose to handle this? irq_inject_interrupt() would work > if I move int-ca55-x and icu-error-ca55 under > irq_domain_create_hierarchy(). Correct. That's how the hierarchy works.
Hi Prabhakar,
On Wed, 21 Jan 2026 at 16:01, Prabhakar <prabhakar.csengg@gmail.com> wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>
> The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT)
> that allows software to explicitly assert interrupts toward individual
> CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding
> interrupt.
>
> Introduce a debug mechanism to trigger software interrupts on individual
> Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind
> CONFIG_DEBUG_FS and a module parameter to ensure it only exists when
> explicitly enabled.
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
Thanks for your patch!
> --- a/drivers/irqchip/irq-renesas-rzv2h.c
> +++ b/drivers/irqchip/irq-renesas-rzv2h.c
> @@ -11,16 +11,23 @@
>
> #include <linux/bitfield.h>
> #include <linux/cleanup.h>
> +#include <linux/cpu.h>
> +#include <linux/debugfs.h>
> #include <linux/err.h>
> +#include <linux/fs.h>
> #include <linux/io.h>
> #include <linux/irqchip.h>
> #include <linux/irqchip/irq-renesas-rzv2h.h>
> #include <linux/irqdomain.h>
> +#include <linux/kconfig.h>
> +#include <linux/kstrtox.h>
> +#include <linux/moduleparam.h>
> #include <linux/of_platform.h>
> #include <linux/pm_runtime.h>
> #include <linux/reset.h>
> #include <linux/spinlock.h>
> #include <linux/syscore_ops.h>
> +#include <linux/uaccess.h>
>
[...]
> +static int rzv2h_icu_setup_debug_irqs(struct platform_device *pdev)
> +{
> + static const u8 swint_idx[ICU_SWINT_NUM] = { 0, 1, 2, 3 };
> + static const char * const rzv2h_swint_names[] = {
> + "int-ca55-0", "int-ca55-1",
> + "int-ca55-2", "int-ca55-3",
> + };
> + struct device *dev = &pdev->dev;
> + struct dentry *dentry;
> + struct dentry *dir;
> + unsigned int i;
> + int icu_irq;
> + int ret;
> +
> + if (!IS_ENABLED(CONFIG_DEBUG_FS) || !enable_icu_debug)
> + return 0;
> +
> + dev_info(dev, "RZ/V2H ICU debug interrupts enabled\n");
> +
> + for (i = 0; i < ICU_SWINT_NUM; i++) {
> + icu_irq = platform_get_irq_byname(pdev, rzv2h_swint_names[i]);
> + if (icu_irq < 0)
> + return dev_err_probe(dev, icu_irq,
> + "Failed to get %s IRQ\n", rzv2h_swint_names[i]);
> + ret = devm_request_irq(dev, icu_irq, rzv2h_icu_swint_irq, 0, dev_name(dev),
> + (void *)&swint_idx[i]);
drivers/irqchip/irq-renesas-rzv2h.c:730:23: error: implicit
declaration of function ‘devm_request_irq’; did you mean
‘can_request_irq’? [-Werror=implicit-function-declaration]
How does this build for you, without including <linux/interrupt.h>?
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
Hi Geert,
Thank you for the review.
On Fri, Jan 23, 2026 at 10:45 AM Geert Uytterhoeven
<geert@linux-m68k.org> wrote:
>
> Hi Prabhakar,
>
> On Wed, 21 Jan 2026 at 16:01, Prabhakar <prabhakar.csengg@gmail.com> wrote:
> > From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> >
> > The Renesas RZ/V2H ICU provides a software interrupt register (ICU_SWINT)
> > that allows software to explicitly assert interrupts toward individual
> > CA55 cores. Writing BIT(n) to ICU_SWINT triggers the corresponding
> > interrupt.
> >
> > Introduce a debug mechanism to trigger software interrupts on individual
> > Cortex-A55 cores via the RZ/V2H ICU. The interface is gated behind
> > CONFIG_DEBUG_FS and a module parameter to ensure it only exists when
> > explicitly enabled.
> >
> > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>
> Thanks for your patch!
>
> > --- a/drivers/irqchip/irq-renesas-rzv2h.c
> > +++ b/drivers/irqchip/irq-renesas-rzv2h.c
> > @@ -11,16 +11,23 @@
> >
> > #include <linux/bitfield.h>
> > #include <linux/cleanup.h>
> > +#include <linux/cpu.h>
> > +#include <linux/debugfs.h>
> > #include <linux/err.h>
> > +#include <linux/fs.h>
> > #include <linux/io.h>
> > #include <linux/irqchip.h>
> > #include <linux/irqchip/irq-renesas-rzv2h.h>
> > #include <linux/irqdomain.h>
> > +#include <linux/kconfig.h>
> > +#include <linux/kstrtox.h>
> > +#include <linux/moduleparam.h>
> > #include <linux/of_platform.h>
> > #include <linux/pm_runtime.h>
> > #include <linux/reset.h>
> > #include <linux/spinlock.h>
> > #include <linux/syscore_ops.h>
> > +#include <linux/uaccess.h>
> >
>
> [...]
>
> > +static int rzv2h_icu_setup_debug_irqs(struct platform_device *pdev)
> > +{
> > + static const u8 swint_idx[ICU_SWINT_NUM] = { 0, 1, 2, 3 };
> > + static const char * const rzv2h_swint_names[] = {
> > + "int-ca55-0", "int-ca55-1",
> > + "int-ca55-2", "int-ca55-3",
> > + };
> > + struct device *dev = &pdev->dev;
> > + struct dentry *dentry;
> > + struct dentry *dir;
> > + unsigned int i;
> > + int icu_irq;
> > + int ret;
> > +
> > + if (!IS_ENABLED(CONFIG_DEBUG_FS) || !enable_icu_debug)
> > + return 0;
> > +
> > + dev_info(dev, "RZ/V2H ICU debug interrupts enabled\n");
> > +
> > + for (i = 0; i < ICU_SWINT_NUM; i++) {
> > + icu_irq = platform_get_irq_byname(pdev, rzv2h_swint_names[i]);
> > + if (icu_irq < 0)
> > + return dev_err_probe(dev, icu_irq,
> > + "Failed to get %s IRQ\n", rzv2h_swint_names[i]);
> > + ret = devm_request_irq(dev, icu_irq, rzv2h_icu_swint_irq, 0, dev_name(dev),
> > + (void *)&swint_idx[i]);
>
> drivers/irqchip/irq-renesas-rzv2h.c:730:23: error: implicit
> declaration of function ‘devm_request_irq’; did you mean
> ‘can_request_irq’? [-Werror=implicit-function-declaration]
>
> How does this build for you, without including <linux/interrupt.h>?
>
While posting the patches, I had rebased them on next-20260119 (and
used defconfig), but I didn't see any build issues. Below is the
snippet from irq-renesas-rzv2h.i:
struct ns_common;
int open_related_ns(struct ns_common *ns, struct ns_common
*(*get_ns)(struct ns_common *ns));
static inline __attribute__((__gnu_inline__))
__attribute__((__unused__))
__attribute__((__no_instrument_function__)) struct pid_namespace
*proc_pid_ns(struct super_block *sb)
{
return proc_sb_info(sb)->pid_ns;
}
bool proc_ns_file(const struct file *file);
# 20 "./include/linux/efi.h" 2
# 1 "./include/linux/rtc.h" 1
# 17 "./include/linux/rtc.h"
# 1 "./include/linux/interrupt.h" 1
# 9 "./include/linux/interrupt.h"
# 1 "./include/linux/irqreturn.h" 1
# 11 "./include/linux/irqreturn.h"
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
# 10 "./include/linux/interrupt.h" 2
# 1 "./include/linux/hardirq.h" 1
Tracing through the above interrupt.h was included in below :
-------------------------------------------------------------------------------
drivers/irqchip/irq-renesas-rzv2h.c - (line 19)
#include <linux/irqchip.h>
#include <linux/acpi.h>
#include <acpi/acpi_io.h>
#include <asm/acpi.h>
#include <linux/efi.h>
#include <linux/rtc.h>
#include <linux/interrupt.h>
Now that you mentioned there was a build issue, I tried with
renesas_defconfig and I do get the build issue which you pointed out.
I'll respin a v2 with #include <linux/interrupt.h> included
explicitly.
Cheers,
Prabhakar
© 2016 - 2026 Red Hat, Inc.