irqchip/sifive-plic: Cleanup PLIC contexts upon irqdomain creation failure
authorAnup Patel <apatel@ventanamicro.com>
Thu, 22 Feb 2024 09:39:53 +0000 (15:09 +0530)
committerThomas Gleixner <tglx@linutronix.de>
Fri, 23 Feb 2024 09:18:44 +0000 (10:18 +0100)
The SiFive PLIC contexts should not be left dangling if irqdomain creation
fails because plic_starting_cpu() can crash accessing unmapped registers.

Signed-off-by: Anup Patel <apatel@ventanamicro.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20240222094006.1030709-6-apatel@ventanamicro.com
drivers/irqchip/irq-sifive-plic.c

index 208fad76f560ee2d9251b4eabefddc2d27c27859..a399cb3f44af55d029060417d87914d04791dfaf 100644 (file)
@@ -417,17 +417,45 @@ static const struct of_device_id plic_match[] = {
        {}
 };
 
+static int plic_parse_context_parent(struct platform_device *pdev, u32 context,
+                                    u32 *parent_hwirq, int *parent_cpu)
+{
+       struct device *dev = &pdev->dev;
+       struct of_phandle_args parent;
+       unsigned long hartid;
+       int rc;
+
+       /*
+        * Currently, only OF fwnode is supported so extend this
+        * function for ACPI support.
+        */
+       if (!is_of_node(dev->fwnode))
+               return -EINVAL;
+
+       rc = of_irq_parse_one(to_of_node(dev->fwnode), context, &parent);
+       if (rc)
+               return rc;
+
+       rc = riscv_of_parent_hartid(parent.np, &hartid);
+       if (rc)
+               return rc;
+
+       *parent_hwirq = parent.args[0];
+       *parent_cpu = riscv_hartid_to_cpuid(hartid);
+       return 0;
+}
+
 static int plic_probe(struct platform_device *pdev)
 {
-       int error = 0, nr_contexts, nr_handlers = 0, i;
+       int error = 0, nr_contexts, nr_handlers = 0, cpu, i;
        struct device *dev = &pdev->dev;
        unsigned long plic_quirks = 0;
        struct plic_handler *handler;
+       u32 nr_irqs, parent_hwirq;
        struct irq_domain *domain;
        struct plic_priv *priv;
+       irq_hw_number_t hwirq;
        bool cpuhp_setup;
-       unsigned int cpu;
-       u32 nr_irqs;
 
        if (is_of_node(dev->fwnode)) {
                const struct of_device_id *id;
@@ -463,13 +491,9 @@ static int plic_probe(struct platform_device *pdev)
                return -EINVAL;
 
        for (i = 0; i < nr_contexts; i++) {
-               struct of_phandle_args parent;
-               irq_hw_number_t hwirq;
-               int cpu;
-               unsigned long hartid;
-
-               if (of_irq_parse_one(to_of_node(dev->fwnode), i, &parent)) {
-                       dev_err(dev, "failed to parse parent for context %d.\n", i);
+               error = plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu);
+               if (error) {
+                       dev_warn(dev, "hwirq for context%d not found\n", i);
                        continue;
                }
 
@@ -477,7 +501,7 @@ static int plic_probe(struct platform_device *pdev)
                 * Skip contexts other than external interrupts for our
                 * privilege level.
                 */
-               if (parent.args[0] != RV_IRQ_EXT) {
+               if (parent_hwirq != RV_IRQ_EXT) {
                        /* Disable S-mode enable bits if running in M-mode. */
                        if (IS_ENABLED(CONFIG_RISCV_M_MODE)) {
                                void __iomem *enable_base = priv->regs +
@@ -490,13 +514,6 @@ static int plic_probe(struct platform_device *pdev)
                        continue;
                }
 
-               error = riscv_of_parent_hartid(parent.np, &hartid);
-               if (error < 0) {
-                       dev_warn(dev, "failed to parse hart ID for context %d.\n", i);
-                       continue;
-               }
-
-               cpu = riscv_hartid_to_cpuid(hartid);
                if (cpu < 0) {
                        dev_warn(dev, "Invalid cpuid for context %d\n", i);
                        continue;
@@ -534,7 +551,7 @@ static int plic_probe(struct platform_device *pdev)
                handler->enable_save = devm_kcalloc(dev, DIV_ROUND_UP(nr_irqs, 32),
                                                    sizeof(*handler->enable_save), GFP_KERNEL);
                if (!handler->enable_save)
-                       return -ENOMEM;
+                       goto fail_cleanup_contexts;
 done:
                for (hwirq = 1; hwirq <= nr_irqs; hwirq++) {
                        plic_toggle(handler, hwirq, 0);
@@ -547,7 +564,7 @@ done:
        priv->irqdomain = irq_domain_add_linear(to_of_node(dev->fwnode), nr_irqs + 1,
                                                &plic_irqdomain_ops, priv);
        if (WARN_ON(!priv->irqdomain))
-               return -ENOMEM;
+               goto fail_cleanup_contexts;
 
        /*
         * We can have multiple PLIC instances so setup cpuhp state
@@ -575,6 +592,22 @@ done:
        dev_info(dev, "mapped %d interrupts with %d handlers for %d contexts.\n",
                 nr_irqs, nr_handlers, nr_contexts);
        return 0;
+
+fail_cleanup_contexts:
+       for (i = 0; i < nr_contexts; i++) {
+               if (plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu))
+                       continue;
+               if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
+                       continue;
+
+               handler = per_cpu_ptr(&plic_handlers, cpu);
+               handler->present = false;
+               handler->hart_base = NULL;
+               handler->enable_base = NULL;
+               handler->enable_save = NULL;
+               handler->priv = NULL;
+       }
+       return -ENOMEM;
 }
 
 static struct platform_driver plic_driver = {