[RFC PATCH v2 3/5] irqchip/realtek-rtl: use per-parent irq handling
From: Sander Vanheule
Date: Sun Dec 26 2021 - 15:02:48 EST
The driver handled all SoC interrupts equally, independent of which
parent interrupt it is routed to. Between all configured inputs, the use
of __ffs actually gives higher priority to lower input lines,
effectively bypassing any priority there might be among the parent
interrupts.
Rework the driver to use a separate handler for each parent interrupt,
to respect the order in which those parents interrupt are handled.
Signed-off-by: Sander Vanheule <sander@xxxxxxxxxxxxx>
---
With switching back to chained handlers, it became impossible to route a
SoC interrupt to the timer interrupt (CPU IRQ 7) on systems using the
CEVT-R4K timer. If a chained handler is already set for the timer
interrupt, the timer cannot request it anymore (due to IRQ_NOREQUEST)
and the system hangs. It is probably a terrible idea to also run e.g.
the console on the timer interrupt, but it is possible. If there are no
solutions to this, I can live with it though; there are still 5 other
interrupts.
Changes since v1:
- Limit scope to per-parent handling
- Replace the "priority" naming with the more generic "output"
- Don't request interrupts, but stick to chained handlers
---
drivers/irqchip/irq-realtek-rtl.c | 109 ++++++++++++++++++++----------
1 file changed, 74 insertions(+), 35 deletions(-)
diff --git a/drivers/irqchip/irq-realtek-rtl.c b/drivers/irqchip/irq-realtek-rtl.c
index 568614edd88f..1f8f21a0bd1a 100644
--- a/drivers/irqchip/irq-realtek-rtl.c
+++ b/drivers/irqchip/irq-realtek-rtl.c
@@ -7,6 +7,7 @@
#include <linux/of_irq.h>
#include <linux/irqchip.h>
+#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/of_address.h>
#include <linux/irqchip/chained_irq.h>
@@ -21,10 +22,45 @@
#define RTL_ICTL_IRR2 0x10
#define RTL_ICTL_IRR3 0x14
+#define RTL_ICTL_NUM_OUTPUTS 6
+
#define REG(x) (realtek_ictl_base + x)
static DEFINE_RAW_SPINLOCK(irq_lock);
static void __iomem *realtek_ictl_base;
+static struct irq_domain *realtek_ictl_domain;
+
+struct realtek_ictl_output {
+ unsigned int routing_value;
+ u32 child_mask;
+};
+
+static struct realtek_ictl_output realtek_ictl_outputs[RTL_ICTL_NUM_OUTPUTS];
+
+/*
+ * IRR0-IRR3 store 4 bits per interrupt, but Realtek uses inverted numbering,
+ * placing IRQ 31 in the first four bits. A routing value of '0' means the
+ * interrupt is left disconnected. Routing values {1..15} connect to output
+ * lines {0..14}.
+ */
+#define IRR_OFFSET(idx) (4 * (3 - (idx * 4) / 32))
+#define IRR_SHIFT(idx) ((idx * 4) % 32)
+
+static inline u32 read_irr(void __iomem *irr0, int idx)
+{
+ return (readl(irr0 + IRR_OFFSET(idx)) >> IRR_SHIFT(idx)) & 0xf;
+}
+
+static inline void write_irr(void __iomem *irr0, int idx, u32 value)
+{
+ unsigned int offset = IRR_OFFSET(idx);
+ unsigned int shift = IRR_SHIFT(idx);
+ u32 irr;
+
+ irr = readl(irr0 + offset) & ~(0xf << shift);
+ irr |= (value & 0xf) << shift;
+ writel(irr, irr0 + offset);
+}
static void realtek_ictl_unmask_irq(struct irq_data *i)
{
@@ -74,42 +110,45 @@ static const struct irq_domain_ops irq_domain_ops = {
static void realtek_irq_dispatch(struct irq_desc *desc)
{
+ struct realtek_ictl_output *parent = irq_desc_get_handler_data(desc);
struct irq_chip *chip = irq_desc_get_chip(desc);
- struct irq_domain *domain;
unsigned int pending;
chained_irq_enter(chip, desc);
- pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR));
+ pending = readl(REG(RTL_ICTL_GIMR)) & readl(REG(RTL_ICTL_GISR))
+ & parent->child_mask;
+
if (unlikely(!pending)) {
spurious_interrupt();
goto out;
}
- domain = irq_desc_get_handler_data(desc);
- generic_handle_domain_irq(domain, __ffs(pending));
+ generic_handle_domain_irq(realtek_ictl_domain, __ffs(pending));
out:
chained_irq_exit(chip, desc);
}
-/*
- * SoC interrupts are cascaded to MIPS CPU interrupts according to the
- * interrupt-map in the device tree. Each SoC interrupt gets 4 bits for
- * the CPU interrupt in an Interrupt Routing Register. Max 32 SoC interrupts
- * thus go into 4 IRRs. A routing value of '0' means the interrupt is left
- * disconnected. Routing values {1..15} connect to output lines {0..14}.
- */
-static int __init map_interrupts(struct device_node *node, struct irq_domain *domain)
+static void __init set_routing(struct realtek_ictl_output *output, unsigned int soc_int)
{
+ unsigned int routing_old;
+
+ routing_old = read_irr(REG(RTL_ICTL_IRR0), soc_int);
+ if (routing_old) {
+ pr_warn("int %d already routed to %d, not updating\n", soc_int, routing_old);
+ return;
+ }
+
+ output->child_mask |= BIT(soc_int);
+ write_irr(REG(RTL_ICTL_IRR0), soc_int, output->routing_value);
+}
+
+static int __init map_interrupts(struct device_node *node)
+{
+ struct realtek_ictl_output *output;
struct device_node *cpu_ictl;
const __be32 *imap;
- u32 imaplen, soc_int, cpu_int, tmp, regs[4];
- int ret, i, irr_regs[] = {
- RTL_ICTL_IRR3,
- RTL_ICTL_IRR2,
- RTL_ICTL_IRR1,
- RTL_ICTL_IRR0,
- };
- u8 mips_irqs_set;
+ u32 imaplen, soc_int, cpu_int, tmp;
+ int ret, i;
ret = of_property_read_u32(node, "#address-cells", &tmp);
if (ret || tmp)
@@ -119,8 +158,6 @@ static int __init map_interrupts(struct device_node *node, struct irq_domain *do
if (!imap || imaplen % 3)
return -EINVAL;
- mips_irqs_set = 0;
- memset(regs, 0, sizeof(regs));
for (i = 0; i < imaplen; i += 3 * sizeof(u32)) {
soc_int = be32_to_cpup(imap);
if (soc_int > 31)
@@ -138,39 +175,41 @@ static int __init map_interrupts(struct device_node *node, struct irq_domain *do
if (cpu_int > 7 || cpu_int < 2)
return -EINVAL;
- if (!(mips_irqs_set & BIT(cpu_int))) {
- irq_set_chained_handler_and_data(cpu_int, realtek_irq_dispatch,
- domain);
- mips_irqs_set |= BIT(cpu_int);
+ output = &realtek_ictl_outputs[cpu_int - 2];
+
+ if (!output->routing_value) {
+ irq_set_chained_handler_and_data(cpu_int, realtek_irq_dispatch, output);
+ /* Use routing values (1..6) for CPU interrupts (2..7) */
+ output->routing_value = cpu_int - 1;
}
- /* Use routing values (1..6) for CPU interrupts (2..7) */
- regs[(soc_int * 4) / 32] |= (cpu_int - 1) << (soc_int * 4) % 32;
+ set_routing(output, soc_int);
+
imap += 3;
}
- for (i = 0; i < 4; i++)
- writel(regs[i], REG(irr_regs[i]));
-
return 0;
}
static int __init realtek_rtl_of_init(struct device_node *node, struct device_node *parent)
{
- struct irq_domain *domain;
+ unsigned int soc_irq;
int ret;
+ memset(&realtek_ictl_outputs, 0, sizeof(realtek_ictl_outputs));
+
realtek_ictl_base = of_iomap(node, 0);
if (!realtek_ictl_base)
return -ENXIO;
/* Disable all cascaded interrupts */
writel(0, REG(RTL_ICTL_GIMR));
+ for (soc_irq = 0; soc_irq < 32; soc_irq++)
+ write_irr(REG(RTL_ICTL_IRR0), soc_irq, 0);
- domain = irq_domain_add_simple(node, 32, 0,
- &irq_domain_ops, NULL);
+ realtek_ictl_domain = irq_domain_add_simple(node, 32, 0, &irq_domain_ops, NULL);
- ret = map_interrupts(node, domain);
+ ret = map_interrupts(node);
if (ret) {
pr_err("invalid interrupt map\n");
return ret;
--
2.33.1