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

irqchip: Add RISC-V incoming MSI controller early driver

The RISC-V advanced interrupt architecture (AIA) specification
defines a new MSI controller called incoming message signalled
interrupt controller (IMSIC) which manages MSI on per-HART (or
per-CPU) basis. It also supports IPIs as software injected MSIs.
(For more details refer https://github.com/riscv/riscv-aia

)

Add an early irqchip driver for RISC-V IMSIC which sets up the
IMSIC state and provide IPIs.

Signed-off-by: default avatarAnup Patel <apatel@ventanamicro.com>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarBjörn Töpel <bjorn@rivosinc.com>
Reviewed-by: default avatarBjörn Töpel <bjorn@rivosinc.com>
Link: https://lore.kernel.org/r/20240307140307.646078-3-apatel@ventanamicro.com
parent 0151a8db
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -540,6 +540,13 @@ config RISCV_INTC
	depends on RISCV
	select IRQ_DOMAIN_HIERARCHY

config RISCV_IMSIC
	bool
	depends on RISCV
	select IRQ_DOMAIN_HIERARCHY
	select GENERIC_IRQ_MATRIX_ALLOCATOR
	select GENERIC_MSI_IRQ

config SIFIVE_PLIC
	bool
	depends on RISCV
+1 −0
Original line number Diff line number Diff line
@@ -95,6 +95,7 @@ obj-$(CONFIG_QCOM_MPM) += irq-qcom-mpm.o
obj-$(CONFIG_CSKY_MPINTC)		+= irq-csky-mpintc.o
obj-$(CONFIG_CSKY_APB_INTC)		+= irq-csky-apb-intc.o
obj-$(CONFIG_RISCV_INTC)		+= irq-riscv-intc.o
obj-$(CONFIG_RISCV_IMSIC)		+= irq-riscv-imsic-state.o irq-riscv-imsic-early.o
obj-$(CONFIG_SIFIVE_PLIC)		+= irq-sifive-plic.o
obj-$(CONFIG_STARFIVE_JH8100_INTC)	+= irq-starfive-jh8100-intc.o
obj-$(CONFIG_IMX_IRQSTEER)		+= irq-imx-irqsteer.o
+201 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2021 Western Digital Corporation or its affiliates.
 * Copyright (C) 2022 Ventana Micro Systems Inc.
 */

#define pr_fmt(fmt) "riscv-imsic: " fmt
#include <linux/cpu.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/irqchip.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/smp.h>

#include "irq-riscv-imsic-state.h"

static int imsic_parent_irq;

#ifdef CONFIG_SMP
static void imsic_ipi_send(unsigned int cpu)
{
	struct imsic_local_config *local = per_cpu_ptr(imsic->global.local, cpu);

	writel_relaxed(IMSIC_IPI_ID, local->msi_va);
}

static void imsic_ipi_starting_cpu(void)
{
	/* Enable IPIs for current CPU. */
	__imsic_id_set_enable(IMSIC_IPI_ID);
}

static void imsic_ipi_dying_cpu(void)
{
	/* Disable IPIs for current CPU. */
	__imsic_id_clear_enable(IMSIC_IPI_ID);
}

static int __init imsic_ipi_domain_init(void)
{
	int virq;

	/* Create IMSIC IPI multiplexing */
	virq = ipi_mux_create(IMSIC_NR_IPI, imsic_ipi_send);
	if (virq <= 0)
		return virq < 0 ? virq : -ENOMEM;

	/* Set vIRQ range */
	riscv_ipi_set_virq_range(virq, IMSIC_NR_IPI, true);

	/* Announce that IMSIC is providing IPIs */
	pr_info("%pfwP: providing IPIs using interrupt %d\n", imsic->fwnode, IMSIC_IPI_ID);

	return 0;
}
#else
static void imsic_ipi_starting_cpu(void) { }
static void imsic_ipi_dying_cpu(void) { }
static int __init imsic_ipi_domain_init(void) { return 0; }
#endif

/*
 * To handle an interrupt, we read the TOPEI CSR and write zero in one
 * instruction. If TOPEI CSR is non-zero then we translate TOPEI.ID to
 * Linux interrupt number and let Linux IRQ subsystem handle it.
 */
static void imsic_handle_irq(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	int err, cpu = smp_processor_id();
	struct imsic_vector *vec;
	unsigned long local_id;

	chained_irq_enter(chip, desc);

	while ((local_id = csr_swap(CSR_TOPEI, 0))) {
		local_id >>= TOPEI_ID_SHIFT;

		if (local_id == IMSIC_IPI_ID) {
			if (IS_ENABLED(CONFIG_SMP))
				ipi_mux_process();
			continue;
		}

		if (unlikely(!imsic->base_domain))
			continue;

		vec = imsic_vector_from_local_id(cpu, local_id);
		if (!vec) {
			pr_warn_ratelimited("vector not found for local ID 0x%lx\n", local_id);
			continue;
		}

		err = generic_handle_domain_irq(imsic->base_domain, vec->hwirq);
		if (unlikely(err))
			pr_warn_ratelimited("hwirq 0x%x mapping not found\n", vec->hwirq);
	}

	chained_irq_exit(chip, desc);
}

static int imsic_starting_cpu(unsigned int cpu)
{
	/* Mark per-CPU IMSIC state as online */
	imsic_state_online();

	/* Enable per-CPU parent interrupt */
	enable_percpu_irq(imsic_parent_irq, irq_get_trigger_type(imsic_parent_irq));

	/* Setup IPIs */
	imsic_ipi_starting_cpu();

	/*
	 * Interrupts identities might have been enabled/disabled while
	 * this CPU was not running so sync-up local enable/disable state.
	 */
	imsic_local_sync_all();

	/* Enable local interrupt delivery */
	imsic_local_delivery(true);

	return 0;
}

static int imsic_dying_cpu(unsigned int cpu)
{
	/* Cleanup IPIs */
	imsic_ipi_dying_cpu();

	/* Mark per-CPU IMSIC state as offline */
	imsic_state_offline();

	return 0;
}

static int __init imsic_early_probe(struct fwnode_handle *fwnode)
{
	struct irq_domain *domain;
	int rc;

	/* Find parent domain and register chained handler */
	domain = irq_find_matching_fwnode(riscv_get_intc_hwnode(), DOMAIN_BUS_ANY);
	if (!domain) {
		pr_err("%pfwP: Failed to find INTC domain\n", fwnode);
		return -ENOENT;
	}
	imsic_parent_irq = irq_create_mapping(domain, RV_IRQ_EXT);
	if (!imsic_parent_irq) {
		pr_err("%pfwP: Failed to create INTC mapping\n", fwnode);
		return -ENOENT;
	}

	/* Initialize IPI domain */
	rc = imsic_ipi_domain_init();
	if (rc) {
		pr_err("%pfwP: Failed to initialize IPI domain\n", fwnode);
		return rc;
	}

	/* Setup chained handler to the parent domain interrupt */
	irq_set_chained_handler(imsic_parent_irq, imsic_handle_irq);

	/*
	 * Setup cpuhp state (must be done after setting imsic_parent_irq)
	 *
	 * Don't disable per-CPU IMSIC file when CPU goes offline
	 * because this affects IPI and the masking/unmasking of
	 * virtual IPIs is done via generic IPI-Mux
	 */
	cpuhp_setup_state(CPUHP_AP_IRQ_RISCV_IMSIC_STARTING, "irqchip/riscv/imsic:starting",
			  imsic_starting_cpu, imsic_dying_cpu);

	return 0;
}

static int __init imsic_early_dt_init(struct device_node *node, struct device_node *parent)
{
	struct fwnode_handle *fwnode = &node->fwnode;
	int rc;

	/* Setup IMSIC state */
	rc = imsic_setup_state(fwnode);
	if (rc) {
		pr_err("%pfwP: failed to setup state (error %d)\n", fwnode, rc);
		return rc;
	}

	/* Do early setup of IPIs */
	rc = imsic_early_probe(fwnode);
	if (rc)
		return rc;

	/* Ensure that OF platform device gets probed */
	of_node_clear_flag(node, OF_POPULATED);
	return 0;
}

IRQCHIP_DECLARE(riscv_imsic, "riscv,imsics", imsic_early_dt_init);
+865 −0

File added.

Preview size limit exceeded, changes collapsed.

+107 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (C) 2021 Western Digital Corporation or its affiliates.
 * Copyright (C) 2022 Ventana Micro Systems Inc.
 */

#ifndef _IRQ_RISCV_IMSIC_STATE_H
#define _IRQ_RISCV_IMSIC_STATE_H

#include <linux/irqchip/riscv-imsic.h>
#include <linux/irqdomain.h>
#include <linux/fwnode.h>
#include <linux/timer.h>

#define IMSIC_IPI_ID				1
#define IMSIC_NR_IPI				8

struct imsic_vector {
	/* Fixed details of the vector */
	unsigned int				cpu;
	unsigned int				local_id;
	/* Details saved by driver in the vector */
	unsigned int				hwirq;
	/* Details accessed using local lock held */
	bool					enable;
	struct imsic_vector			*move;
};

struct imsic_local_priv {
	/* Local lock to protect vector enable/move variables and dirty bitmap */
	raw_spinlock_t				lock;

	/* Local dirty bitmap for synchronization */
	unsigned long				*dirty_bitmap;

#ifdef CONFIG_SMP
	/* Local timer for synchronization */
	struct timer_list			timer;
#endif

	/* Local vector table */
	struct imsic_vector			*vectors;
};

struct imsic_priv {
	/* Device details */
	struct fwnode_handle			*fwnode;

	/* Global configuration common for all HARTs */
	struct imsic_global_config		global;

	/* Per-CPU state */
	struct imsic_local_priv __percpu	*lpriv;

	/* State of IRQ matrix allocator */
	raw_spinlock_t				matrix_lock;
	struct irq_matrix			*matrix;

	/* IRQ domains (created by platform driver) */
	struct irq_domain			*base_domain;
};

extern struct imsic_priv *imsic;

void __imsic_eix_update(unsigned long base_id, unsigned long num_id, bool pend, bool val);

static inline void __imsic_id_set_enable(unsigned long id)
{
	__imsic_eix_update(id, 1, false, true);
}

static inline void __imsic_id_clear_enable(unsigned long id)
{
	__imsic_eix_update(id, 1, false, false);
}

void imsic_local_sync_all(void);
void imsic_local_delivery(bool enable);

void imsic_vector_mask(struct imsic_vector *vec);
void imsic_vector_unmask(struct imsic_vector *vec);

static inline bool imsic_vector_isenabled(struct imsic_vector *vec)
{
	return READ_ONCE(vec->enable);
}

static inline struct imsic_vector *imsic_vector_get_move(struct imsic_vector *vec)
{
	return READ_ONCE(vec->move);
}

void imsic_vector_move(struct imsic_vector *old_vec, struct imsic_vector *new_vec);

struct imsic_vector *imsic_vector_from_local_id(unsigned int cpu, unsigned int local_id);

struct imsic_vector *imsic_vector_alloc(unsigned int hwirq, const struct cpumask *mask);
void imsic_vector_free(struct imsic_vector *vector);

void imsic_vector_debug_show(struct seq_file *m, struct imsic_vector *vec, int ind);
void imsic_vector_debug_show_summary(struct seq_file *m, int ind);

void imsic_state_online(void);
void imsic_state_offline(void);
int imsic_setup_state(struct fwnode_handle *fwnode);

#endif
Loading