Commit 4d936f10 authored by Anup Patel's avatar Anup Patel Committed by Thomas Gleixner

irqchip/sifive-plic: Probe plic driver early for Allwinner D1 platform

The latest Linux RISC-V no longer boots on the Allwinner D1 platform
because the sun4i_timer driver fails to get an interrupt from PLIC due to
the recent conversion of the PLIC to a platform driver. Converting the
sun4i timer to a platform driver does not work either because the D1 does
not have a SBI timer available so early boot hangs. See the 'Closes:'
link for deeper analysis.

The real fix requires enabling the SBI time extension in the platform
firmware (OpenSBI) and convert sun4i_timer into platform driver.
Unfortunately, the real fix involves changing multiple places and can't be
achieved in a short duration and aside of that requires users to update
firmware.

As a work-around, retrofit PLIC probing such that the PLIC is probed early
only for the Allwinner D1 platform and probed as a regular platform driver
for rest of the RISC-V platforms. In the process, partially revert some of
the previous changes because the PLIC device pointer is not available in
all probing paths.

Fixes: e306a894 ("irqchip/sifive-plic: Chain to parent IRQ after handlers are ready")
Fixes: 8ec99b03 ("irqchip/sifive-plic: Convert PLIC driver into a platform driver")
Suggested-by: default avatarThomas Gleixner <tglx@linutronix.de>
Signed-off-by: default avatarAnup Patel <apatel@ventanamicro.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarSamuel Holland <samuel.holland@sifive.com>
Tested-by: default avatarEmil Renner Berthing <emil.renner.berthing@canonical.com>
Tested-by: default avatarCharlie Jenkins <charlie@rivosinc.com>
Reviewed-by: default avatarSamuel Holland <samuel.holland@sifive.com>
Reviewed-by: default avatarCharlie Jenkins <charlie@rivosinc.com>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/20240820034850.3189912-1-apatel@ventanamicro.com
Closes: https://lore.kernel.org/lkml/20240814145642.344485-1-emil.renner.berthing@canonical.com/
parent 47ac09b9
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
* Copyright (C) 2017 SiFive * Copyright (C) 2017 SiFive
* Copyright (C) 2018 Christoph Hellwig * Copyright (C) 2018 Christoph Hellwig
*/ */
#define pr_fmt(fmt) "riscv-plic: " fmt
#include <linux/cpu.h> #include <linux/cpu.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/io.h> #include <linux/io.h>
...@@ -63,7 +64,7 @@ ...@@ -63,7 +64,7 @@
#define PLIC_QUIRK_EDGE_INTERRUPT 0 #define PLIC_QUIRK_EDGE_INTERRUPT 0
struct plic_priv { struct plic_priv {
struct device *dev; struct fwnode_handle *fwnode;
struct cpumask lmask; struct cpumask lmask;
struct irq_domain *irqdomain; struct irq_domain *irqdomain;
void __iomem *regs; void __iomem *regs;
...@@ -378,8 +379,8 @@ static void plic_handle_irq(struct irq_desc *desc) ...@@ -378,8 +379,8 @@ static void plic_handle_irq(struct irq_desc *desc)
int err = generic_handle_domain_irq(handler->priv->irqdomain, int err = generic_handle_domain_irq(handler->priv->irqdomain,
hwirq); hwirq);
if (unlikely(err)) { if (unlikely(err)) {
dev_warn_ratelimited(handler->priv->dev, pr_warn_ratelimited("%pfwP: can't find mapping for hwirq %lu\n",
"can't find mapping for hwirq %lu\n", hwirq); handler->priv->fwnode, hwirq);
} }
} }
...@@ -408,7 +409,8 @@ static int plic_starting_cpu(unsigned int cpu) ...@@ -408,7 +409,8 @@ static int plic_starting_cpu(unsigned int cpu)
enable_percpu_irq(plic_parent_irq, enable_percpu_irq(plic_parent_irq,
irq_get_trigger_type(plic_parent_irq)); irq_get_trigger_type(plic_parent_irq));
else else
dev_warn(handler->priv->dev, "cpu%d: parent irq not available\n", cpu); pr_warn("%pfwP: cpu%d: parent irq not available\n",
handler->priv->fwnode, cpu);
plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD); plic_set_threshold(handler, PLIC_ENABLE_THRESHOLD);
return 0; return 0;
...@@ -424,38 +426,36 @@ static const struct of_device_id plic_match[] = { ...@@ -424,38 +426,36 @@ static const struct of_device_id plic_match[] = {
{} {}
}; };
static int plic_parse_nr_irqs_and_contexts(struct platform_device *pdev, static int plic_parse_nr_irqs_and_contexts(struct fwnode_handle *fwnode,
u32 *nr_irqs, u32 *nr_contexts) u32 *nr_irqs, u32 *nr_contexts)
{ {
struct device *dev = &pdev->dev;
int rc; int rc;
/* /*
* Currently, only OF fwnode is supported so extend this * Currently, only OF fwnode is supported so extend this
* function for ACPI support. * function for ACPI support.
*/ */
if (!is_of_node(dev->fwnode)) if (!is_of_node(fwnode))
return -EINVAL; return -EINVAL;
rc = of_property_read_u32(to_of_node(dev->fwnode), "riscv,ndev", nr_irqs); rc = of_property_read_u32(to_of_node(fwnode), "riscv,ndev", nr_irqs);
if (rc) { if (rc) {
dev_err(dev, "riscv,ndev property not available\n"); pr_err("%pfwP: riscv,ndev property not available\n", fwnode);
return rc; return rc;
} }
*nr_contexts = of_irq_count(to_of_node(dev->fwnode)); *nr_contexts = of_irq_count(to_of_node(fwnode));
if (WARN_ON(!(*nr_contexts))) { if (WARN_ON(!(*nr_contexts))) {
dev_err(dev, "no PLIC context available\n"); pr_err("%pfwP: no PLIC context available\n", fwnode);
return -EINVAL; return -EINVAL;
} }
return 0; return 0;
} }
static int plic_parse_context_parent(struct platform_device *pdev, u32 context, static int plic_parse_context_parent(struct fwnode_handle *fwnode, u32 context,
u32 *parent_hwirq, int *parent_cpu) u32 *parent_hwirq, int *parent_cpu)
{ {
struct device *dev = &pdev->dev;
struct of_phandle_args parent; struct of_phandle_args parent;
unsigned long hartid; unsigned long hartid;
int rc; int rc;
...@@ -464,10 +464,10 @@ static int plic_parse_context_parent(struct platform_device *pdev, u32 context, ...@@ -464,10 +464,10 @@ static int plic_parse_context_parent(struct platform_device *pdev, u32 context,
* Currently, only OF fwnode is supported so extend this * Currently, only OF fwnode is supported so extend this
* function for ACPI support. * function for ACPI support.
*/ */
if (!is_of_node(dev->fwnode)) if (!is_of_node(fwnode))
return -EINVAL; return -EINVAL;
rc = of_irq_parse_one(to_of_node(dev->fwnode), context, &parent); rc = of_irq_parse_one(to_of_node(fwnode), context, &parent);
if (rc) if (rc)
return rc; return rc;
...@@ -480,48 +480,55 @@ static int plic_parse_context_parent(struct platform_device *pdev, u32 context, ...@@ -480,48 +480,55 @@ static int plic_parse_context_parent(struct platform_device *pdev, u32 context,
return 0; return 0;
} }
static int plic_probe(struct platform_device *pdev) static int plic_probe(struct fwnode_handle *fwnode)
{ {
int error = 0, nr_contexts, nr_handlers = 0, cpu, i; int error = 0, nr_contexts, nr_handlers = 0, cpu, i;
struct device *dev = &pdev->dev;
unsigned long plic_quirks = 0; unsigned long plic_quirks = 0;
struct plic_handler *handler; struct plic_handler *handler;
u32 nr_irqs, parent_hwirq; u32 nr_irqs, parent_hwirq;
struct plic_priv *priv; struct plic_priv *priv;
irq_hw_number_t hwirq; irq_hw_number_t hwirq;
void __iomem *regs;
if (is_of_node(dev->fwnode)) { if (is_of_node(fwnode)) {
const struct of_device_id *id; const struct of_device_id *id;
id = of_match_node(plic_match, to_of_node(dev->fwnode)); id = of_match_node(plic_match, to_of_node(fwnode));
if (id) if (id)
plic_quirks = (unsigned long)id->data; plic_quirks = (unsigned long)id->data;
regs = of_iomap(to_of_node(fwnode), 0);
if (!regs)
return -ENOMEM;
} else {
return -ENODEV;
} }
error = plic_parse_nr_irqs_and_contexts(pdev, &nr_irqs, &nr_contexts); error = plic_parse_nr_irqs_and_contexts(fwnode, &nr_irqs, &nr_contexts);
if (error) if (error)
return error; goto fail_free_regs;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) if (!priv) {
return -ENOMEM; error = -ENOMEM;
goto fail_free_regs;
}
priv->dev = dev; priv->fwnode = fwnode;
priv->plic_quirks = plic_quirks; priv->plic_quirks = plic_quirks;
priv->nr_irqs = nr_irqs; priv->nr_irqs = nr_irqs;
priv->regs = regs;
priv->regs = devm_platform_ioremap_resource(pdev, 0); priv->prio_save = bitmap_zalloc(nr_irqs, GFP_KERNEL);
if (WARN_ON(!priv->regs)) if (!priv->prio_save) {
return -EIO; error = -ENOMEM;
goto fail_free_priv;
priv->prio_save = devm_bitmap_zalloc(dev, nr_irqs, GFP_KERNEL); }
if (!priv->prio_save)
return -ENOMEM;
for (i = 0; i < nr_contexts; i++) { for (i = 0; i < nr_contexts; i++) {
error = plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu); error = plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu);
if (error) { if (error) {
dev_warn(dev, "hwirq for context%d not found\n", i); pr_warn("%pfwP: hwirq for context%d not found\n", fwnode, i);
continue; continue;
} }
...@@ -543,7 +550,7 @@ static int plic_probe(struct platform_device *pdev) ...@@ -543,7 +550,7 @@ static int plic_probe(struct platform_device *pdev)
} }
if (cpu < 0) { if (cpu < 0) {
dev_warn(dev, "Invalid cpuid for context %d\n", i); pr_warn("%pfwP: Invalid cpuid for context %d\n", fwnode, i);
continue; continue;
} }
...@@ -554,7 +561,7 @@ static int plic_probe(struct platform_device *pdev) ...@@ -554,7 +561,7 @@ static int plic_probe(struct platform_device *pdev)
*/ */
handler = per_cpu_ptr(&plic_handlers, cpu); handler = per_cpu_ptr(&plic_handlers, cpu);
if (handler->present) { if (handler->present) {
dev_warn(dev, "handler already present for context %d.\n", i); pr_warn("%pfwP: handler already present for context %d.\n", fwnode, i);
plic_set_threshold(handler, PLIC_DISABLE_THRESHOLD); plic_set_threshold(handler, PLIC_DISABLE_THRESHOLD);
goto done; goto done;
} }
...@@ -568,7 +575,7 @@ static int plic_probe(struct platform_device *pdev) ...@@ -568,7 +575,7 @@ static int plic_probe(struct platform_device *pdev)
i * CONTEXT_ENABLE_SIZE; i * CONTEXT_ENABLE_SIZE;
handler->priv = priv; handler->priv = priv;
handler->enable_save = devm_kcalloc(dev, DIV_ROUND_UP(nr_irqs, 32), handler->enable_save = kcalloc(DIV_ROUND_UP(nr_irqs, 32),
sizeof(*handler->enable_save), GFP_KERNEL); sizeof(*handler->enable_save), GFP_KERNEL);
if (!handler->enable_save) if (!handler->enable_save)
goto fail_cleanup_contexts; goto fail_cleanup_contexts;
...@@ -581,7 +588,7 @@ static int plic_probe(struct platform_device *pdev) ...@@ -581,7 +588,7 @@ static int plic_probe(struct platform_device *pdev)
nr_handlers++; nr_handlers++;
} }
priv->irqdomain = irq_domain_add_linear(to_of_node(dev->fwnode), nr_irqs + 1, priv->irqdomain = irq_domain_add_linear(to_of_node(fwnode), nr_irqs + 1,
&plic_irqdomain_ops, priv); &plic_irqdomain_ops, priv);
if (WARN_ON(!priv->irqdomain)) if (WARN_ON(!priv->irqdomain))
goto fail_cleanup_contexts; goto fail_cleanup_contexts;
...@@ -619,13 +626,13 @@ static int plic_probe(struct platform_device *pdev) ...@@ -619,13 +626,13 @@ static int plic_probe(struct platform_device *pdev)
} }
} }
dev_info(dev, "mapped %d interrupts with %d handlers for %d contexts.\n", pr_info("%pfwP: mapped %d interrupts with %d handlers for %d contexts.\n",
nr_irqs, nr_handlers, nr_contexts); fwnode, nr_irqs, nr_handlers, nr_contexts);
return 0; return 0;
fail_cleanup_contexts: fail_cleanup_contexts:
for (i = 0; i < nr_contexts; i++) { for (i = 0; i < nr_contexts; i++) {
if (plic_parse_context_parent(pdev, i, &parent_hwirq, &cpu)) if (plic_parse_context_parent(fwnode, i, &parent_hwirq, &cpu))
continue; continue;
if (parent_hwirq != RV_IRQ_EXT || cpu < 0) if (parent_hwirq != RV_IRQ_EXT || cpu < 0)
continue; continue;
...@@ -634,17 +641,37 @@ static int plic_probe(struct platform_device *pdev) ...@@ -634,17 +641,37 @@ static int plic_probe(struct platform_device *pdev)
handler->present = false; handler->present = false;
handler->hart_base = NULL; handler->hart_base = NULL;
handler->enable_base = NULL; handler->enable_base = NULL;
kfree(handler->enable_save);
handler->enable_save = NULL; handler->enable_save = NULL;
handler->priv = NULL; handler->priv = NULL;
} }
return -ENOMEM; bitmap_free(priv->prio_save);
fail_free_priv:
kfree(priv);
fail_free_regs:
iounmap(regs);
return error;
}
static int plic_platform_probe(struct platform_device *pdev)
{
return plic_probe(pdev->dev.fwnode);
} }
static struct platform_driver plic_driver = { static struct platform_driver plic_driver = {
.driver = { .driver = {
.name = "riscv-plic", .name = "riscv-plic",
.of_match_table = plic_match, .of_match_table = plic_match,
.suppress_bind_attrs = true,
}, },
.probe = plic_probe, .probe = plic_platform_probe,
}; };
builtin_platform_driver(plic_driver); builtin_platform_driver(plic_driver);
static int __init plic_early_probe(struct device_node *node,
struct device_node *parent)
{
return plic_probe(&node->fwnode);
}
IRQCHIP_DECLARE(riscv, "allwinner,sun20i-d1-plic", plic_early_probe);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment