2024-02-22 09:42:45

by Anup Patel

[permalink] [raw]
Subject: [PATCH v14 05/18] irqchip/sifive-plic: Cleanup PLIC contexts upon irqdomain creation failure

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 <[email protected]>
---
drivers/irqchip/irq-sifive-plic.c | 73 ++++++++++++++++++++++---------
1 file changed, 53 insertions(+), 20 deletions(-)

diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c
index 615498071b6e..f0df5d0cb76e 100644
--- a/drivers/irqchip/irq-sifive-plic.c
+++ b/drivers/irqchip/irq-sifive-plic.c
@@ -416,17 +416,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;
@@ -462,13 +490,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;
}

@@ -476,7 +500,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 +
@@ -489,13 +513,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 @@ static int plic_probe(struct platform_device *pdev)
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 @@ static int plic_probe(struct platform_device *pdev)
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 = {
--
2.34.1



2024-02-23 09:51:41

by tip-bot2 for Tony Luck

[permalink] [raw]
Subject: [tip: irq/msi] irqchip/sifive-plic: Cleanup PLIC contexts upon irqdomain creation failure

The following commit has been merged into the irq/msi branch of tip:

Commit-ID: a15587277a246c388c83b1cd9cf7c1a868cd752f
Gitweb: https://git.kernel.org/tip/a15587277a246c388c83b1cd9cf7c1a868cd752f
Author: Anup Patel <[email protected]>
AuthorDate: Thu, 22 Feb 2024 15:09:53 +05:30
Committer: Thomas Gleixner <[email protected]>
CommitterDate: Fri, 23 Feb 2024 10:18:44 +01:00

irqchip/sifive-plic: Cleanup PLIC contexts upon irqdomain creation failure

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 <[email protected]>
Signed-off-by: Thomas Gleixner <[email protected]>
Link: https://lore.kernel.org/r/[email protected]

---
drivers/irqchip/irq-sifive-plic.c | 73 +++++++++++++++++++++---------
1 file changed, 53 insertions(+), 20 deletions(-)

diff --git a/drivers/irqchip/irq-sifive-plic.c b/drivers/irqchip/irq-sifive-plic.c
index 208fad7..a399cb3 100644
--- a/drivers/irqchip/irq-sifive-plic.c
+++ b/drivers/irqchip/irq-sifive-plic.c
@@ -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 = {