[PATCH] of/irq: parse interrupts-extended during irq init heirarchy calculation

From: Ben Dooks
Date: Fri Jul 08 2022 - 12:52:36 EST


When the irq controler code works out the heirarchy for initialialisation
it only looks at interrupt-parent properties, but controllers such as the
RISC-V PLIC use a extended-interrupt property and therefore do not get
properly considered during initialisation.

This means that if anything changes in the driver initialisation order
then the PLIC can get called before the CLINT nodes, and thus interrupts
do not get configured properly and the init continues without noticing
the error until drivers fail due to having no interrupts delivered.

Add code to the of_irq_init that checks for the extended-interrupt
property and adds these parent nodes so that they can be considered
during the calculations of whether an irq controller node can be
initialised.

Signed-off-by: Ben Dooks <ben.dooks@xxxxxxxxxx>
---
drivers/of/irq.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 58 insertions(+), 2 deletions(-)

diff --git a/drivers/of/irq.c b/drivers/of/irq.c
index d22f605fa7ee..abfee1fb8717 100644
--- a/drivers/of/irq.c
+++ b/drivers/of/irq.c
@@ -507,11 +507,32 @@ EXPORT_SYMBOL_GPL(of_irq_to_resource_table);

struct of_intc_desc {
struct list_head list;
+ struct list_head parents;
of_irq_init_cb_t irq_init_cb;
struct device_node *dev;
struct device_node *interrupt_parent;
};

+static bool of_irq_init_check_parents(struct of_intc_desc *desc,
+ struct device_node *parent)
+{
+ struct of_intc_desc *search, *temp_desc;
+
+ if (list_empty(&desc->parents))
+ return desc->interrupt_parent == parent;
+
+ list_for_each_entry_safe(search, temp_desc, &desc->parents, list) {
+ if (search->interrupt_parent == parent) {
+ pr_debug("%s: %pOF removing %pOF\n",
+ __func__, desc->dev, search->interrupt_parent);
+ list_del(&search->list);
+ kfree(search);
+ }
+ }
+
+ return list_empty(&desc->parents);
+}
+
/**
* of_irq_init - Scan and init matching interrupt controllers in DT
* @matches: 0 terminated array of nodes to match and init function to call
@@ -548,6 +569,7 @@ void __init of_irq_init(const struct of_device_id *matches)
goto err;
}

+ INIT_LIST_HEAD(&desc->parents);
desc->irq_init_cb = match->data;
desc->dev = of_node_get(np);
/*
@@ -563,6 +585,40 @@ void __init of_irq_init(const struct of_device_id *matches)
desc->interrupt_parent = NULL;
}
list_add_tail(&desc->list, &intc_desc_list);
+
+ /*
+ * If the irq controller has an interrupts-extended
+ * property then go through it to find out if there
+ * are any parents in there to consider.
+ */
+ if (!desc->interrupt_parent &&
+ of_find_property(np, "interrupts-extended", NULL)) {
+ struct of_phandle_args irq;
+ int nr_irqs = of_irq_count(np);
+ int index, res;
+
+ pr_debug("%s: %pOF interrupts-extended, %d irqs\n",
+ __func__, np, nr_irqs);
+
+ for (index = 0; index < nr_irqs; index++) {
+ res = of_parse_phandle_with_args(np, "interrupts-extended",
+ "#interrupt-cells", index, &irq);
+ if (res) {
+ goto err;
+ }
+
+ pr_debug("%s: %pOF parent %pOF\n", __func__, np, irq.np);
+ if (irq.np == np)
+ continue;
+
+ temp_desc = kzalloc(sizeof(*temp_desc), GFP_KERNEL);
+ if (!temp_desc)
+ goto err;
+
+ temp_desc->interrupt_parent = irq.np;
+ list_add_tail(&temp_desc->list, &desc->parents);
+ }
+ }
}

/*
@@ -579,14 +635,14 @@ void __init of_irq_init(const struct of_device_id *matches)
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
int ret;

- if (desc->interrupt_parent != parent)
+ if (!of_irq_init_check_parents(desc, parent))
continue;

list_del(&desc->list);

of_node_set_flag(desc->dev, OF_POPULATED);

- pr_debug("of_irq_init: init %pOF (%p), parent %p\n",
+ pr_debug("of_irq_init: init %pOF (%px), parent %px\n",
desc->dev,
desc->dev, desc->interrupt_parent);
ret = desc->irq_init_cb(desc->dev,
--
2.35.1