Commit e306a894 authored by Samuel Holland's avatar Samuel Holland Committed by Thomas Gleixner
Browse files

irqchip/sifive-plic: Chain to parent IRQ after handlers are ready



Now that the PLIC uses a platform driver, the driver is probed later in the
boot process, where interrupts from peripherals might already be pending.

As a result, plic_handle_irq() may be called as early as the call to
irq_set_chained_handler() completes. But this call happens before the
per-context handler is completely set up, so there is a window where
plic_handle_irq() can see incomplete per-context state and crash.

Avoid this by delaying the call to irq_set_chained_handler() until all
handlers from all PLICs are initialized.

Fixes: 8ec99b03 ("irqchip/sifive-plic: Convert PLIC driver into a platform driver")
Reported-by: default avatarGeert Uytterhoeven <geert@linux-m68k.org>
Signed-off-by: default avatarSamuel Holland <samuel.holland@sifive.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
Reviewed-by: default avatarAnup Patel <anup@brainfault.org>
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20240529215458.937817-1-samuel.holland@sifive.com
Closes: https://lore.kernel.org/r/CAMuHMdVYFFR7K5SbHBLY-JHhb7YpgGMS_hnRWm8H0KD-wBo+4A@mail.gmail.com/
parent 0110c4b1
Loading
Loading
Loading
Loading
+17 −17
Original line number Diff line number Diff line
@@ -85,7 +85,7 @@ struct plic_handler {
	struct plic_priv	*priv;
};
static int plic_parent_irq __ro_after_init;
static bool plic_cpuhp_setup_done __ro_after_init;
static bool plic_global_setup_done __ro_after_init;
static DEFINE_PER_CPU(struct plic_handler, plic_handlers);

static int plic_irq_set_type(struct irq_data *d, unsigned int type);
@@ -487,10 +487,8 @@ static int plic_probe(struct platform_device *pdev)
	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;

	if (is_of_node(dev->fwnode)) {
		const struct of_device_id *id;
@@ -549,14 +547,6 @@ static int plic_probe(struct platform_device *pdev)
			continue;
		}

		/* Find parent domain and register chained handler */
		domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY);
		if (!plic_parent_irq && domain) {
			plic_parent_irq = irq_create_mapping(domain, RV_IRQ_EXT);
			if (plic_parent_irq)
				irq_set_chained_handler(plic_parent_irq, plic_handle_irq);
		}

		/*
		 * When running in M-mode we need to ignore the S-mode handler.
		 * Here we assume it always comes later, but that might be a
@@ -597,25 +587,35 @@ static int plic_probe(struct platform_device *pdev)
		goto fail_cleanup_contexts;

	/*
	 * We can have multiple PLIC instances so setup cpuhp state
	 * We can have multiple PLIC instances so setup global state
	 * and register syscore operations only once after context
	 * handlers of all online CPUs are initialized.
	 */
	if (!plic_cpuhp_setup_done) {
		cpuhp_setup = true;
	if (!plic_global_setup_done) {
		struct irq_domain *domain;
		bool global_setup = true;

		for_each_online_cpu(cpu) {
			handler = per_cpu_ptr(&plic_handlers, cpu);
			if (!handler->present) {
				cpuhp_setup = false;
				global_setup = false;
				break;
			}
		}
		if (cpuhp_setup) {

		if (global_setup) {
			/* Find parent domain and register chained handler */
			domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY);
			if (domain)
				plic_parent_irq = irq_create_mapping(domain, RV_IRQ_EXT);
			if (plic_parent_irq)
				irq_set_chained_handler(plic_parent_irq, plic_handle_irq);

			cpuhp_setup_state(CPUHP_AP_IRQ_SIFIVE_PLIC_STARTING,
					  "irqchip/sifive/plic:starting",
					  plic_starting_cpu, plic_dying_cpu);
			register_syscore_ops(&plic_irq_syscore_ops);
			plic_cpuhp_setup_done = true;
			plic_global_setup_done = true;
		}
	}