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, Caleb James DeLisle <cjd@cjdns.fr>
Subject: [PATCH 2/2] irqchip/econet-en751221: Support MIPS 34Kc VEIC mode
Date: Sat, 25 Apr 2026 12:35:31 +0000 [thread overview]
Message-ID: <20260425123531.270548-3-cjd@cjdns.fr> (raw)
In-Reply-To: <20260425123531.270548-1-cjd@cjdns.fr>
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 we have the task of making a
reasonably modular driver - but for a device which in fact ends up
taking over the entire interrupt system.
We 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
*/
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;
--
2.39.5
next prev parent reply other threads:[~2026-04-25 12:40 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-25 12:35 [PATCH 0/2] irqchip/econet-en751221: Support MIPS 34Kc VEIC mode Caleb James DeLisle
2026-04-25 12:35 ` [PATCH 1/2] dt-bindings: interrupt-controller: econet: Add CPU interrupt mapping Caleb James DeLisle
2026-04-25 13:28 ` Rob Herring (Arm)
2026-04-25 17:03 ` Caleb James DeLisle
2026-04-25 12:35 ` Caleb James DeLisle [this message]
2026-04-29 7:19 ` [PATCH 2/2] irqchip/econet-en751221: Support MIPS 34Kc VEIC mode Thomas Gleixner
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=20260425123531.270548-3-cjd@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