Commit a1558727 authored by Anup Patel's avatar Anup Patel Committed by Thomas Gleixner
Browse files

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: default avatarAnup Patel <apatel@ventanamicro.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Link: https://lore.kernel.org/r/20240222094006.1030709-6-apatel@ventanamicro.com
parent 6c725f33
Loading
Loading
Loading
Loading
+53 −20
Original line number Diff line number Diff line
@@ -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 @@ 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 = {