git: ee91dae43d23 - main - riscv: Introduce support for APLIC interrupt controller

From: Mitchell Horne <mhorne_at_FreeBSD.org>
Date: Wed, 14 Feb 2024 15:42:58 UTC
The branch main has been updated by mhorne:

URL: https://cgit.FreeBSD.org/src/commit/?id=ee91dae43d23a3fa94dca1c905157e66c73c45de

commit ee91dae43d23a3fa94dca1c905157e66c73c45de
Author:     Himanshu Chauhan <himanshu@thchauhan.dev>
AuthorDate: 2024-02-14 15:31:26 +0000
Commit:     Mitchell Horne <mhorne@FreeBSD.org>
CommitDate: 2024-02-14 15:42:29 +0000

    riscv: Introduce support for APLIC interrupt controller
    
    This patch introduces support for the RISC-V APLIC interrupt controller
    [1]. Currently, it is only supports direct mode, i.e. without an IMSIC
    and functionally replacing the legacy RISC-V PLIC. Work on IMSIC support
    is in progress.
    
    [1] https://github.com/riscv/riscv-aia/releases/tag/1.0
    
    Reviewed by:    mhorne
    Discussed with: jrtc27
    MFC after:      1 month
    Differential Revision:  https://reviews.freebsd.org/D43293
---
 sys/conf/files.riscv    |   1 +
 sys/riscv/riscv/aplic.c | 554 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 555 insertions(+)

diff --git a/sys/conf/files.riscv b/sys/conf/files.riscv
index b6a87c63422b..be7ae2b40a08 100644
--- a/sys/conf/files.riscv
+++ b/sys/conf/files.riscv
@@ -27,6 +27,7 @@ libkern/memset.c		standard
 libkern/strcmp.c		standard
 libkern/strlen.c		standard
 libkern/strncmp.c		standard
+riscv/riscv/aplic.c		standard
 riscv/riscv/autoconf.c		standard
 riscv/riscv/bus_machdep.c	standard
 riscv/riscv/bus_space_asm.S	standard
diff --git a/sys/riscv/riscv/aplic.c b/sys/riscv/riscv/aplic.c
new file mode 100644
index 000000000000..19b80409c0f2
--- /dev/null
+++ b/sys/riscv/riscv/aplic.c
@@ -0,0 +1,554 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2024 Himanshu Chauhan <hchauhan@thechauhan.dev>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/ktr.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/rman.h>
+#include <sys/smp.h>
+
+#include <machine/bus.h>
+#include <machine/intr.h>
+#include <machine/riscvreg.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include "pic_if.h"
+
+#define	APLIC_MAX_IRQS		1023
+
+/* Smaller priority number means higher priority */
+#define	APLIC_INTR_DEF_PRIO	1
+
+static pic_disable_intr_t	aplic_disable_intr;
+static pic_enable_intr_t	aplic_enable_intr;
+static pic_map_intr_t		aplic_map_intr;
+static pic_setup_intr_t		aplic_setup_intr;
+static pic_post_ithread_t	aplic_post_ithread;
+static pic_pre_ithread_t	aplic_pre_ithread;
+static pic_bind_intr_t		aplic_bind_intr;
+
+struct aplic_irqsrc {
+	struct intr_irqsrc isrc;
+	u_int irq;
+};
+
+struct aplic_softc {
+	device_t dev;
+	struct resource *mem_res;
+	struct resource *irq_res;
+	void *ih;
+	struct aplic_irqsrc isrcs[APLIC_MAX_IRQS + 1];
+	unsigned int hart_indices[MAXCPU];
+	int ndev;
+};
+
+#define	APLIC_DOMAIN_CFG_IE		(1UL << 8)	/* Enable domain IRQs */
+#define	APLIC_DOMAIN_CFG_DM		(1UL << 2)	/* IRQ delivery mode */
+#define	APLIC_DOMAIN_CFG_BE		(1UL << 0)	/* Endianess */
+
+#define	APLIC_MODE_DIRECT		0	/* Direct delivery mode */
+#define	APLIC_MODE_MSI			1	/* MSI delivery mode */
+
+#define	APLIC_SRC_CFG_DLGT		(1UL << 10)	/* Source delegation */
+#define	APLIC_SRC_CFG_SM_SHIFT		0
+#define	APLIC_SRC_CFG_SM_MASK		(0x7UL << APLIC_SRC_CFG_SM_SHIFT)
+
+#define	APLIC_SRC_CFG_SM_INACTIVE	0	/* APLIC inactive in domain */
+#define	APLIC_SRC_CFG_SM_DETACHED	1	/* Detached from source wire */
+#define	APLIC_SRC_CFG_SM_EDGE_RSE	4	/* Asserted on rising edge */
+#define	APLIC_SRC_CFG_SM_EDGE_FLL	5	/* Asserted on falling edge */
+#define	APLIC_SRC_CFG_SM_LVL_HI		6	/* Asserted when high */
+#define	APLIC_SRC_CFG_SM_LVL_LO		7	/* Asserted when low */
+
+/* Register offsets in APLIC configuration space */
+#define	APLIC_DOMAIN_CFG		0x0000
+#define	APLIC_SRC_CFG(_idx)		(0x0004 + (((_idx) - 1) * 4))
+#define	APLIC_TARGET(_idx)		(0x3004 + (((_idx) - 1) * 4))
+#define	APLIC_MMSIADDRCFG		0x1BC0
+#define	APLIC_MMSIADDRCFGH		0x1BC4
+#define	APLIC_SMSIADDRCFG		0x1BC8
+#define	APLIC_SMSIADDRCFGH		0x1BCC
+#define	APLIC_SETIPNUM			0x1CDC
+#define	APLIC_CLRIPNUM			0x1DDC
+#define	APLIC_SETIENUM			0x1EDC
+#define	APLIC_CLRIENUM			0x1FDC
+#define	APLIC_SETIPNUM_LE		0x2000
+#define	APLIC_SETIPNUM_BE		0x2004
+#define	APLIC_GENMSI			0x3000
+#define	APLIC_IDC_BASE			0x4000
+
+#define	APLIC_SETIE_BASE		0x1E00
+#define	APLIC_CLRIE_BASE		0x1F00
+
+/* Interrupt delivery control structure */
+#define	APLIC_IDC_IDELIVERY_OFFS	0x0000
+#define	APLIC_IDC_IFORCE_OFFS		0x0004
+#define	APLIC_IDC_ITHRESHOLD_OFFS	0x0008
+#define	APLIC_IDC_TOPI_OFFS		0x0018
+#define	APLIC_IDC_CLAIMI_OFFS		0x001C
+
+#define	APLIC_IDC_SZ			0x20
+
+#define	APLIC_IDC_IDELIVERY_DISABLE	0
+#define	APLIC_IDC_IDELIVERY_ENABLE	1
+#define	APLIC_IDC_ITHRESHOLD_DISABLE	0
+
+#define	APLIC_IDC_CLAIMI_PRIO_MASK	0xff
+#define	APLIC_IDC_CLAIMI_IRQ_SHIFT	16
+#define	APLIC_IDC_CLAIMI_IRQ_MASK	0x3ff
+
+#define	APLIC_IDC_CLAIMI_IRQ(_claimi)			\
+	(((_claimi) >> APLIC_IDC_CLAIMI_IRQ_SHIFT)	\
+	    & APLIC_IDC_CLAIMI_IRQ_MASK)
+
+#define	APLIC_IDC_CLAIMI_PRIO(_claimi)	((_claimi) & APLIC_IDC_CLAIMI_PRIO_MASK)
+
+#define	APLIC_IDC_REG(_sc, _cpu, _field)		\
+	(APLIC_IDC_BASE + APLIC_IDC_##_field##_OFFS +	\
+	    ((_sc->hart_indices[_cpu]) * APLIC_IDC_SZ))
+
+#define	APLIC_IDC_IDELIVERY(_sc, _cpu)			\
+	APLIC_IDC_REG(_sc, _cpu, IDELIVERY)
+
+#define	APLIC_IDC_IFORCE(_sc, _cpu)			\
+	APLIC_IDC_REG(_sc, _cpu, IFORCE)
+
+#define	APLIC_IDC_ITHRESHOLD(_sc, _cpu)			\
+	APLIC_IDC_REG(_sc, _cpu, ITHRESHOLD)
+
+#define	APLIC_IDC_TOPI(_sc, _cpu)			\
+	APLIC_IDC_REG(_sc, _cpu, TOPI)
+
+#define	APLIC_IDC_CLAIMI(_sc, _cpu)			\
+	APLIC_IDC_REG(_sc, _cpu, CLAIMI)
+
+#define	APLIC_MK_IRQ_TARGET(_sc, _cpu, _prio)		\
+	(_sc->hart_indices[_cpu] << 18 | ((_prio) & 0xff))
+
+#define	aplic_read(sc, reg)		bus_read_4(sc->mem_res, (reg))
+#define	aplic_write(sc, reg, val)	bus_write_4(sc->mem_res, (reg), (val))
+
+static u_int aplic_irq_cpu;
+
+static inline int
+riscv_cpu_to_hartid(int cpu)
+{
+	return pcpu_find(cpu)->pc_hart;
+}
+
+static inline int
+riscv_hartid_to_cpu(int hartid)
+{
+	int cpu;
+
+	CPU_FOREACH(cpu) {
+		if (riscv_cpu_to_hartid(cpu) == hartid)
+			return cpu;
+	}
+
+	return (-1);
+}
+
+static int
+fdt_get_hartid(device_t dev, phandle_t aplic)
+{
+	int hartid;
+
+	/* Check the interrupt controller layout. */
+	if (OF_searchencprop(aplic, "#interrupt-cells", &hartid,
+	    sizeof(hartid)) == -1) {
+		device_printf(dev,
+		    "Could not find #interrupt-cells for phandle %u\n", aplic);
+		return (-1);
+	}
+
+	/*
+	 * The parent of the interrupt-controller is the CPU we are
+	 * interested in, so search for its hart ID.
+	 */
+	if (OF_searchencprop(OF_parent(aplic), "reg", (pcell_t *)&hartid,
+	    sizeof(hartid)) == -1) {
+		device_printf(dev, "Could not find hartid\n");
+		return (-1);
+	}
+
+	return (hartid);
+}
+
+static inline void
+aplic_irq_dispatch(struct aplic_softc *sc, u_int irq, u_int prio,
+    struct trapframe *tf)
+{
+	struct aplic_irqsrc *src;
+
+	src = &sc->isrcs[irq];
+
+	if (intr_isrc_dispatch(&src->isrc, tf) != 0)
+		if (bootverbose)
+			device_printf(sc->dev, "Stray irq %u detected\n", irq);
+}
+
+static int
+aplic_intr(void *arg)
+{
+	struct aplic_softc *sc;
+	struct trapframe *tf;
+	u_int claimi, prio, irq;
+	int cpu;
+
+	sc = arg;
+	cpu = PCPU_GET(cpuid);
+
+	/* Claim any pending interrupt. */
+	claimi = aplic_read(sc, APLIC_IDC_CLAIMI(sc, cpu));
+	prio = APLIC_IDC_CLAIMI_PRIO(claimi);
+	irq = APLIC_IDC_CLAIMI_IRQ(claimi);
+
+	KASSERT((irq != 0), ("Invalid IRQ 0"));
+
+	tf = curthread->td_intr_frame;
+	aplic_irq_dispatch(sc, irq, prio, tf);
+
+	return (FILTER_HANDLED);
+}
+
+static void
+aplic_disable_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct aplic_softc *sc;
+	struct aplic_irqsrc *src;
+
+	sc = device_get_softc(dev);
+	src = (struct aplic_irqsrc *)isrc;
+
+	/* Disable the interrupt source */
+	aplic_write(sc, APLIC_CLRIENUM, src->irq);
+}
+
+static void
+aplic_enable_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct aplic_softc *sc;
+	struct aplic_irqsrc *src;
+
+	sc = device_get_softc(dev);
+	src = (struct aplic_irqsrc *)isrc;
+
+	/* Enable the interrupt source */
+	aplic_write(sc, APLIC_SETIENUM, src->irq);
+}
+
+static int
+aplic_map_intr(device_t dev, struct intr_map_data *data,
+    struct intr_irqsrc **isrcp)
+{
+	struct intr_map_data_fdt *daf;
+	struct aplic_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	if (data->type != INTR_MAP_DATA_FDT)
+		return (ENOTSUP);
+
+	daf = (struct intr_map_data_fdt *)data;
+	if (daf->ncells != 2 || daf->cells[0] > sc->ndev) {
+		device_printf(dev, "Invalid cell data\n");
+		return (EINVAL);
+	}
+
+	*isrcp = &sc->isrcs[daf->cells[0]].isrc;
+
+	return (0);
+}
+
+static int
+aplic_probe(device_t dev)
+{
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+
+	if (!ofw_bus_is_compatible(dev, "riscv,aplic"))
+		return (ENXIO);
+
+	device_set_desc(dev, "Advanced Platform-Level Interrupt Controller");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+/*
+ * Setup APLIC in direct mode.
+ */
+static int
+aplic_setup_direct_mode(device_t dev)
+{
+	struct aplic_irqsrc *isrcs;
+	struct aplic_softc *sc;
+	struct intr_pic *pic;
+	const char *name;
+	phandle_t node, xref, iparent;
+	pcell_t *cells, cell;
+	int error = ENXIO;
+	u_int irq;
+	int cpu, hartid, rid, i, nintr, idc;
+
+	sc = device_get_softc(dev);
+	node = ofw_bus_get_node(dev);
+
+	sc->dev = dev;
+
+	if ((OF_getencprop(node, "riscv,num-sources", &sc->ndev,
+	    sizeof(sc->ndev))) < 0) {
+		device_printf(dev, "Error: could not get number of devices\n");
+		return (error);
+	}
+
+	if (sc->ndev > APLIC_MAX_IRQS) {
+		device_printf(dev, "Error: invalid ndev (%d)\n", sc->ndev);
+		return (error);
+	}
+
+	/* Request memory resources */
+	rid = 0;
+	sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+	    RF_ACTIVE);
+	if (sc->mem_res == NULL) {
+		device_printf(dev,
+		    "Error: could not allocate memory resources\n");
+		return (error);
+	}
+
+	/* Set APLIC in direct mode and enable all interrupts */
+	aplic_write(sc, APLIC_DOMAIN_CFG,
+	    APLIC_MODE_DIRECT | APLIC_DOMAIN_CFG_IE);
+
+	/* Register the interrupt sources */
+	isrcs = sc->isrcs;
+	name = device_get_nameunit(sc->dev);
+	for (irq = 1; irq <= sc->ndev; irq++) {
+		isrcs[irq].irq = irq;
+
+		error = intr_isrc_register(&isrcs[irq].isrc, sc->dev,
+		    0, "%s,%u", name, irq);
+		if (error != 0)
+			goto fail;
+
+		aplic_write(sc, APLIC_SRC_CFG(irq),
+		    APLIC_SRC_CFG_SM_DETACHED);
+	}
+
+	nintr = OF_getencprop_alloc_multi(node, "interrupts-extended",
+	    sizeof(uint32_t), (void **)&cells);
+	if (nintr <= 0) {
+		device_printf(dev, "Could not read interrupts-extended\n");
+		goto fail;
+	}
+
+	/* interrupts-extended is a list of phandles and interrupt types. */
+	for (i = 0, idc = 0; i < nintr; i += 2, idc++) {
+		/* Skip M-mode external interrupts */
+		if (cells[i + 1] != IRQ_EXTERNAL_SUPERVISOR)
+			continue;
+
+		/* Get the hart ID from the CLIC's phandle. */
+		hartid = fdt_get_hartid(dev, OF_node_from_xref(cells[i]));
+		if (hartid < 0) {
+			OF_prop_free(cells);
+			goto fail;
+		}
+
+		/* Get the corresponding cpuid. */
+		cpu = riscv_hartid_to_cpu(hartid);
+		if (cpu < 0) {
+			device_printf(dev, "Invalid cpu for hart %d\n", hartid);
+			OF_prop_free(cells);
+			goto fail;
+		}
+
+		sc->hart_indices[cpu] = idc;
+	}
+	OF_prop_free(cells);
+
+	/* Turn off the interrupt delivery for all CPUs within or out domain */
+	CPU_FOREACH(cpu) {
+		aplic_write(sc, APLIC_IDC_IDELIVERY(sc, cpu),
+		    APLIC_IDC_IDELIVERY_DISABLE);
+		aplic_write(sc, APLIC_IDC_ITHRESHOLD(sc, cpu),
+		    APLIC_IDC_ITHRESHOLD_DISABLE);
+	}
+
+	iparent = OF_xref_from_node(ofw_bus_get_node(intr_irq_root_dev));
+	cell = IRQ_EXTERNAL_SUPERVISOR;
+	irq = ofw_bus_map_intr(dev, iparent, 1, &cell);
+	error = bus_set_resource(dev, SYS_RES_IRQ, 0, irq, 1);
+	if (error != 0) {
+		device_printf(dev, "Unable to register IRQ resource\n");
+		return (ENXIO);
+	}
+
+	rid = 0;
+	sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+	    RF_ACTIVE);
+	if (sc->irq_res == NULL) {
+		device_printf(dev,
+		    "Error: could not allocate IRQ resources\n");
+		return (ENXIO);
+	}
+
+	xref = OF_xref_from_node(node);
+	pic = intr_pic_register(sc->dev, xref);
+	if (pic == NULL) {
+		error = ENXIO;
+		goto fail;
+	}
+
+	error = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_CLK,
+	    aplic_intr, NULL, sc, &sc->ih);
+	if (error != 0) {
+		device_printf(dev, "Unable to setup IRQ resource\n");
+		return (ENXIO);
+	}
+
+fail:
+	return (error);
+}
+
+static int
+aplic_attach(device_t dev)
+{
+	int rc;
+
+	/* APLIC with IMSIC on hart is not supported */
+	if (ofw_bus_has_prop(dev, "msi-parent")) {
+		device_printf(dev, "APLIC with IMSIC is unsupported\n");
+		return (ENXIO);
+	}
+
+	rc = aplic_setup_direct_mode(dev);
+
+	return (rc);
+}
+
+static void
+aplic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
+{
+	aplic_disable_intr(dev, isrc);
+}
+
+static void
+aplic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
+{
+	aplic_enable_intr(dev, isrc);
+}
+
+static int
+aplic_setup_intr(device_t dev, struct intr_irqsrc *isrc, struct resource *res,
+    struct intr_map_data *data)
+{
+	struct aplic_irqsrc *src;
+	struct aplic_softc *sc;
+
+	CPU_ZERO(&isrc->isrc_cpu);
+
+	sc = device_get_softc(dev);
+	src = (struct aplic_irqsrc *)isrc;
+
+	aplic_write(sc, APLIC_SRC_CFG(src->irq), APLIC_SRC_CFG_SM_EDGE_RSE);
+
+	/*
+	 * In uniprocessor system, bind_intr will not be called.
+	 * So bind the interrupt on this CPU. If secondary CPUs
+	 * are present, then bind_intr will be called again and
+	 * interrupts will rebind to those CPUs.
+	 */
+	aplic_bind_intr(dev, isrc);
+
+	return (0);
+}
+
+static int
+aplic_bind_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct aplic_softc *sc;
+	struct aplic_irqsrc *src;
+	uint32_t cpu, hartid;
+
+	sc = device_get_softc(dev);
+	src = (struct aplic_irqsrc *)isrc;
+
+	/* Disable the interrupt source */
+	aplic_write(sc, APLIC_CLRIENUM, src->irq);
+
+	if (CPU_EMPTY(&isrc->isrc_cpu)) {
+		cpu = aplic_irq_cpu = intr_irq_next_cpu(aplic_irq_cpu,
+		    &all_cpus);
+		CPU_SETOF(cpu, &isrc->isrc_cpu);
+	} else {
+		cpu = CPU_FFS(&isrc->isrc_cpu) - 1;
+	}
+
+	hartid = riscv_cpu_to_hartid(cpu);
+
+	if (bootverbose)
+		device_printf(dev, "Bind irq %d to cpu%d (hart %d)\n", src->irq,
+		    cpu, hartid);
+
+	aplic_write(sc, APLIC_TARGET(src->irq),
+	    APLIC_MK_IRQ_TARGET(sc, cpu, APLIC_INTR_DEF_PRIO));
+	aplic_write(sc, APLIC_IDC_IDELIVERY(sc, cpu),
+	    APLIC_IDC_IDELIVERY_ENABLE);
+	aplic_enable_intr(dev, isrc);
+
+	return (0);
+}
+
+static device_method_t aplic_methods[] = {
+	DEVMETHOD(device_probe,		aplic_probe),
+	DEVMETHOD(device_attach,	aplic_attach),
+
+	DEVMETHOD(pic_disable_intr,	aplic_disable_intr),
+	DEVMETHOD(pic_enable_intr,	aplic_enable_intr),
+	DEVMETHOD(pic_map_intr,		aplic_map_intr),
+	DEVMETHOD(pic_pre_ithread,	aplic_pre_ithread),
+	DEVMETHOD(pic_post_ithread,	aplic_post_ithread),
+	DEVMETHOD(pic_post_filter,	aplic_post_ithread),
+	DEVMETHOD(pic_setup_intr,	aplic_setup_intr),
+	DEVMETHOD(pic_bind_intr,	aplic_bind_intr),
+
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_0(aplic, aplic_driver, aplic_methods, sizeof(struct aplic_softc));
+
+EARLY_DRIVER_MODULE(aplic, simplebus, aplic_driver, 0, 0,
+    BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);