From: Caleb James DeLisle <cjd@cjdns.fr>
To: linux-mips@vger.kernel.org
Cc: tglx@kernel.org, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org
Subject: Re: [PATCH v2 2/2] irqchip/econet-en751221: Support MIPS 34Kc VEIC mode
Date: Thu, 30 Apr 2026 17:13:38 +0200 [thread overview]
Message-ID: <add19314-5cab-4c5b-9727-28c618690906@cjdns.fr> (raw)
In-Reply-To: <20260430150809.1827881-3-cjd@cjdns.fr>
On 30/04/2026 17:08, Caleb James DeLisle wrote:
> The Vectored External Interrupt Controller mode present in the MIPS
> 34Kc and 1004Kc causes the CPU to stop dispatching interrupts by the
> normal code path and instead it sends those interrupts to the external
> interrupt controller to be prioritized, renumbered, and sent back.
> When they come back, they are handled through a different path using a
> dispatch table, so plat_irq_dispatch never sees action.
>
> This of course subverts the traditional intc hierarchy, and on the
> 1004Kc the interrupt controller is standardized (IRQ_GIC) so it can be
> reasonably considered part of the CPU itself - and tighter coupling
> between IRQ_GIC and arch/mips/* is tolerable. However on the 34Kc
> the intc is defined by each SoC vendor, so it's required to have a
> modular driver - but for a device which in fact ends up taking over the
> entire interrupt system.
>
> Let the DT describe which IRQs which come from the CPU and should be
> routed back and handled by the CPU intc. These particularly include the
> two IPI interrupts which would otherwise necessitate duplication of all
> the IPI supporting infrastructure from the CPU intc.
>
> Signed-off-by: Caleb James DeLisle <cjd@cjdns.fr>
> ---
> drivers/irqchip/irq-econet-en751221.c | 176 +++++++++++++++++++++++++-
> 1 file changed, 173 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/irqchip/irq-econet-en751221.c b/drivers/irqchip/irq-econet-en751221.c
> index d83d5eb12795..98c109fe053b 100644
> --- a/drivers/irqchip/irq-econet-en751221.c
> +++ b/drivers/irqchip/irq-econet-en751221.c
> @@ -30,6 +30,8 @@
> #include <linux/irqchip.h>
> #include <linux/irqchip/chained_irq.h>
>
> +#include <asm/setup.h>
> +
> #define IRQ_COUNT 40
>
> #define NOT_PERCPU 0xff
> @@ -42,14 +44,18 @@
>
> /**
> * @membase: Base address of the interrupt controller registers
> + * @domain: The irq_domain for direct dispatch
> + * @ipi_domain: The irq_domain for inter-process dispatch
> * @interrupt_shadows: Array of all interrupts, for each value,
> * - NOT_PERCPU: This interrupt is not per-cpu, so it has no shadow
> * - IS_SHADOW: This interrupt is a shadow of another per-cpu interrupt
> * - else: This is a per-cpu interrupt whose shadow is the value
Hey I'm really sorry, I did fix replace these spaces with tabs but
Something Happened. I'll resend :(
Caleb
> */
> static struct {
> - void __iomem *membase;
> - u8 interrupt_shadows[IRQ_COUNT];
> + void __iomem *membase;
> + struct irq_domain *domain;
> + struct irq_domain *ipi_domain;
> + u8 interrupt_shadows[IRQ_COUNT];
> } econet_intc __ro_after_init;
>
> static DEFINE_RAW_SPINLOCK(irq_lock);
> @@ -150,6 +156,55 @@ static void econet_intc_from_parent(struct irq_desc *desc)
> chained_irq_exit(chip, desc);
> }
>
> +/* When in VEIC mode, the CPU jumps to a handler in the vector table.
> + * The only way to know which interrupt is being triggered is from the vector table offset that
> + * has been jumped to. Reading REG_PENDING(0|1) will tell you which interrupts are currently
> + * pending in the intc, but that will not tell you which one the intc wants you to process
> + * right now. And if you are not processing the exact interrupt that the intc wants you to be
> + * processing, you might be on the wrong VPE. You can't tell which VPE any given REG_PENDING
> + * interrupt is intended for (shadow IRQ numbers are for masking only, they never flag as
> + * pending).
> + *
> + * Consequently, this little ritual of generating n handler functions and registering one per
> + * interrupt is unavoidable.
> + */
> +#define X(irq) \
> + static void econet_irq_dispatch ## irq (void) \
> + { \
> + do_domain_IRQ(econet_intc.domain, irq); \
> + }
> +
> + X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9)
> +X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19)
> +X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29)
> +X(30) X(31) X(32) X(33) X(34) X(35) X(36) X(37) X(38) X(39)
> +
> +#undef X
> +#define X(irq) econet_irq_dispatch ## irq,
> +
> +static void (* const econet_irq_dispatchers[])(void) = {
> + X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9)
> + X(10) X(11) X(12) X(13) X(14) X(15) X(16) X(17) X(18) X(19)
> + X(20) X(21) X(22) X(23) X(24) X(25) X(26) X(27) X(28) X(29)
> + X(30) X(31) X(32) X(33) X(34) X(35) X(36) X(37) X(38) X(39)
> +};
> +
> +/* Likewise, we do the same for the 2 IPI IRQs so that we can route them back */
> +static void econet_cpu_dispatch0(void)
> +{
> + do_domain_IRQ(econet_intc.ipi_domain, 0);
> +}
> +
> +static void econet_cpu_dispatch1(void)
> +{
> + do_domain_IRQ(econet_intc.ipi_domain, 1);
> +}
> +
> +static void (* const econet_cpu_dispatchers[])(void) = {
> + econet_cpu_dispatch0,
> + econet_cpu_dispatch1,
> +};
> +
> static const struct irq_chip econet_irq_chip;
>
> static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
> @@ -174,6 +229,10 @@ static int econet_intc_map(struct irq_domain *d, u32 irq, irq_hw_number_t hwirq)
> }
>
> irq_set_chip_data(irq, NULL);
> +
> + if (cpu_has_veic)
> + set_vi_handler(hwirq + 1, econet_irq_dispatchers[hwirq]);
> +
> return 0;
> }
>
> @@ -249,6 +308,101 @@ static int __init get_shadow_interrupts(struct device_node *node)
> return 0;
> }
>
> +/**
> + * econet_cpu_init() - configure routing of CPU interrupts to the correct domain.
> + * @node: The devicetree node of this interrupt controller.
> + *
> + * Interrupts that originate from the CPU are unconditionally unmasked here and are re-routed back
> + * to the IPI irq_domain in the CPU intc. Masking still takes place but the CPU intc is in charge
> + * of it, using the mask bits of the c0_status register.
> + *
> + * Note that because IP2 ... IP7 are repurposed as Interrupt Priority Level, only the two IPI
> + * interrupts are actually supported.
> + */
> +static int __init econet_cpu_init(struct device_node *node)
> +{
> + const char *field = "econet,cpu-interrupt-map";
> + struct device_node *parent_intc;
> + int map_size;
> + u32 mask;
> +
> + map_size = of_property_count_u32_elems(node, field);
> +
> + if (map_size <= 0) {
> + return 0;
> + } else if (map_size % 2) {
> + pr_err("%pOF: %s count is odd, ignoring\n", node, field);
> + return 0;
> + }
> +
> + u32 *maps __free(kfree) = kmalloc_array(map_size, sizeof(u32), GFP_KERNEL);
> + if (!maps)
> + return -ENOMEM;
> +
> + if (of_property_read_u32_array(node, field, maps, map_size)) {
> + pr_err("%pOF: Failed to read %s\n", node, field);
> + return -EINVAL;
> + }
> +
> + /* Validation */
> + for (int i = 0; i < map_size; i += 2) {
> + u32 receive = maps[i];
> + u32 dispatch = maps[i + 1];
> + u8 shadow;
> +
> + if (receive >= IRQ_COUNT) {
> + pr_err("%pOF: Entry %d:%d in %s (%u) %s\n",
> + node, i, 0, field, receive, "is out of bounds");
> + return -EINVAL;
> + }
> +
> + shadow = econet_intc.interrupt_shadows[receive];
> + if (shadow != NOT_PERCPU && shadow >= IRQ_COUNT) {
> + pr_err("%pOF: Entry %d:%d in %s (%u) %s\n",
> + node, i, 0, field, receive, "has invalid shadow");
> + return -EINVAL;
> + }
> +
> + if (dispatch >= ARRAY_SIZE(econet_cpu_dispatchers)) {
> + pr_err("%pOF: Entry %d:%d in %s (%u) %s\n",
> + node, i, 1, field, dispatch,
> + "is out of bounds, only IPI interrupts are supported");
> + return -EINVAL;
> + }
> + }
> +
> + parent_intc = of_irq_find_parent(node);
> + if (!parent_intc) {
> + pr_err("%pOF: Failed to find parent %s\n", node, "IRQ device");
> + return -ENODEV;
> + }
> +
> + econet_intc.ipi_domain = irq_find_matching_host(parent_intc, DOMAIN_BUS_IPI);
> + if (!econet_intc.ipi_domain) {
> + pr_err("%pOF: Failed to find parent %s\n", node, "IPI domain");
> + return -ENODEV;
> + }
> +
> + mask = 0;
> + for (int i = 0; i < map_size; i += 2) {
> + u32 receive = maps[i];
> + u32 dispatch = maps[i + 1];
> + u8 shadow;
> +
> + set_vi_handler(receive + 1, econet_cpu_dispatchers[dispatch]);
> +
> + mask |= BIT(receive);
> +
> + shadow = econet_intc.interrupt_shadows[receive];
> + if (shadow != NOT_PERCPU)
> + mask |= BIT(shadow);
> + }
> +
> + econet_wreg(REG_MASK0, mask, mask);
> +
> + return 0;
> +}
> +
> static int __init econet_intc_of_init(struct device_node *node, struct device_node *parent)
> {
> struct irq_domain *domain;
> @@ -294,7 +448,23 @@ static int __init econet_intc_of_init(struct device_node *node, struct device_no
> goto err_unmap;
> }
>
> - irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
> + /*
> + * 34K Manual (MD00534) Section 6.3.1.3 rev 1.13 page 136:
> + * In VEIC mode, IP2 ... IP7 are repurposed as Interrupt Priority Level. The controller
> + * will filter incoming interrupts whose priority is lower than the IPL number. Therefore
> + * we must not set any of these bits. We avoid setting IP2 by not actually chaining this
> + * intc to the CPU intc.
> + */
> + if (cpu_has_veic) {
> + ret = econet_cpu_init(node);
> +
> + if (ret)
> + return ret;
> + } else {
> + irq_set_chained_handler_and_data(irq, econet_intc_from_parent, domain);
> + }
> +
> + econet_intc.domain = domain;
>
> return 0;
>
prev parent reply other threads:[~2026-04-30 15:13 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-30 15:08 [PATCH v2 0/2] irqchip/econet-en751221: Support MIPS 34Kc VEIC mode Caleb James DeLisle
2026-04-30 15:08 ` [PATCH v2 1/2] dt-bindings: interrupt-controller: econet: Add CPU interrupt mapping Caleb James DeLisle
2026-04-30 15:08 ` [PATCH v2 2/2] irqchip/econet-en751221: Support MIPS 34Kc VEIC mode Caleb James DeLisle
2026-04-30 15:13 ` Caleb James DeLisle [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=add19314-5cab-4c5b-9727-28c618690906@cjdns.fr \
--to=cjd@cjdns.fr \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mips@vger.kernel.org \
--cc=robh@kernel.org \
--cc=tglx@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox