aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCyprien Laplace <cyprien@cypou.net>2020-12-05 15:47:33 +0000
committerEd Maste <emaste@FreeBSD.org>2021-03-14 16:26:06 +0000
commite6e8762dff593cbd1da4cc5a3b8f75b183c23837 (patch)
treefec7cda7277be7ef1825d0daed16be8fff012b7c
parent9ac43bb7470b94ca249d091017bab29363a7ed65 (diff)
downloadsrc-e6e8762dff593cbd1da4cc5a3b8f75b183c23837.tar.gz
src-e6e8762dff593cbd1da4cc5a3b8f75b183c23837.zip
gic_v3: add message based interrupts support
Pull Request: https://github.com/freebsd/freebsd-src/pull/451 (cherry picked from commit 35ebd8d33ad2f7c2038f6bf9aa02eab21252f689) (cherry picked from commit acd69b070403e12742ccea7b19d251320ed788d5) Approved by: re (gjb)
-rw-r--r--sys/arm64/arm64/gic_v3.c204
-rw-r--r--sys/arm64/arm64/gic_v3_fdt.c20
-rw-r--r--sys/arm64/arm64/gic_v3_var.h5
3 files changed, 226 insertions, 3 deletions
diff --git a/sys/arm64/arm64/gic_v3.c b/sys/arm64/arm64/gic_v3.c
index 8630a27102e3..954ed3cd878a 100644
--- a/sys/arm64/arm64/gic_v3.c
+++ b/sys/arm64/arm64/gic_v3.c
@@ -71,6 +71,7 @@ __FBSDID("$FreeBSD$");
#endif
#include "pic_if.h"
+#include "msi_if.h"
#include <arm/arm/gic_common.h>
#include "gic_v3_reg.h"
@@ -94,6 +95,12 @@ static pic_ipi_send_t gic_v3_ipi_send;
static pic_ipi_setup_t gic_v3_ipi_setup;
#endif
+static msi_alloc_msi_t gic_v3_alloc_msi;
+static msi_release_msi_t gic_v3_release_msi;
+static msi_alloc_msix_t gic_v3_alloc_msix;
+static msi_release_msix_t gic_v3_release_msix;
+static msi_map_msi_t gic_v3_map_msi;
+
static u_int gic_irq_cpu;
#ifdef SMP
static u_int sgi_to_ipi[GIC_LAST_SGI - GIC_FIRST_SGI + 1];
@@ -124,6 +131,13 @@ static device_method_t gic_v3_methods[] = {
DEVMETHOD(pic_ipi_setup, gic_v3_ipi_setup),
#endif
+ /* MSI/MSI-X */
+ DEVMETHOD(msi_alloc_msi, gic_v3_alloc_msi),
+ DEVMETHOD(msi_release_msi, gic_v3_release_msi),
+ DEVMETHOD(msi_alloc_msix, gic_v3_alloc_msix),
+ DEVMETHOD(msi_release_msix, gic_v3_release_msix),
+ DEVMETHOD(msi_map_msi, gic_v3_map_msi),
+
/* End */
DEVMETHOD_END
};
@@ -150,6 +164,11 @@ struct gic_v3_irqsrc {
uint32_t gi_irq;
enum intr_polarity gi_pol;
enum intr_trigger gi_trig;
+#define GI_FLAG_MSI (1 << 1) /* This interrupt source should only */
+ /* be used for MSI/MSI-X interrupts */
+#define GI_FLAG_MSI_USED (1 << 2) /* This irq is already allocated */
+ /* for a MSI/MSI-X interrupt */
+ u_int gi_flags;
};
/* Helper routines starting with gic_v3_ */
@@ -314,6 +333,22 @@ gic_v3_attach(device_t dev)
}
}
+ if (sc->gic_mbi_start > 0) {
+ /* Reserve these interrupts for MSI/MSI-X use */
+ for (irq = sc->gic_mbi_start; irq <= sc->gic_mbi_end; irq++) {
+ sc->gic_irqs[irq].gi_pol = INTR_POLARITY_HIGH;
+ sc->gic_irqs[irq].gi_trig = INTR_TRIGGER_EDGE;
+ sc->gic_irqs[irq].gi_flags |= GI_FLAG_MSI;
+ }
+
+ mtx_init(&sc->gic_mbi_mtx, "GICv3 mbi lock", NULL, MTX_DEF);
+
+ if (bootverbose) {
+ device_printf(dev, "using spi %u to %u\n", sc->gic_mbi_start,
+ sc->gic_mbi_end);
+ }
+ }
+
/*
* Read the Peripheral ID2 register. This is an implementation
* defined register, but seems to be implemented in all GICv3
@@ -692,8 +727,11 @@ gic_v3_setup_intr(device_t dev, struct intr_irqsrc *isrc,
return (0);
}
- gi->gi_pol = pol;
- gi->gi_trig = trig;
+ /* For MSI/MSI-X we should have already configured these */
+ if ((gi->gi_flags & GI_FLAG_MSI) == 0) {
+ gi->gi_pol = pol;
+ gi->gi_trig = trig;
+ }
/*
* XXX - In case that per CPU interrupt is going to be enabled in time
@@ -742,7 +780,7 @@ gic_v3_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
{
struct gic_v3_irqsrc *gi = (struct gic_v3_irqsrc *)isrc;
- if (isrc->isrc_handlers == 0) {
+ if (isrc->isrc_handlers == 0 && (gi->gi_flags & GI_FLAG_MSI) == 0) {
gi->gi_pol = INTR_POLARITY_CONFORM;
gi->gi_trig = INTR_TRIGGER_CONFORM;
}
@@ -1270,3 +1308,163 @@ gic_v3_redist_init(struct gic_v3_softc *sc)
return (0);
}
+
+/*
+ * SPI-mapped Message Based Interrupts -- a GICv3 MSI/MSI-X controller.
+ */
+
+static int
+gic_v3_alloc_msi(device_t dev, device_t child, int count, int maxcount,
+ device_t *pic, struct intr_irqsrc **srcs)
+{
+ struct gic_v3_softc *sc;
+ int i, irq, end_irq;
+ bool found;
+
+ KASSERT(powerof2(count), ("%s: bad count", __func__));
+ KASSERT(powerof2(maxcount), ("%s: bad maxcount", __func__));
+
+ sc = device_get_softc(dev);
+
+ mtx_lock(&sc->gic_mbi_mtx);
+
+ found = false;
+ for (irq = sc->gic_mbi_start; irq < sc->gic_mbi_end; irq++) {
+ /* Start on an aligned interrupt */
+ if ((irq & (maxcount - 1)) != 0)
+ continue;
+
+ /* Assume we found a valid range until shown otherwise */
+ found = true;
+
+ /* Check this range is valid */
+ for (end_irq = irq; end_irq != irq + count; end_irq++) {
+ /* No free interrupts */
+ if (end_irq == sc->gic_mbi_end) {
+ found = false;
+ break;
+ }
+
+ KASSERT((sc->gic_irqs[end_irq].gi_flags & GI_FLAG_MSI)!= 0,
+ ("%s: Non-MSI interrupt found", __func__));
+
+ /* This is already used */
+ if ((sc->gic_irqs[end_irq].gi_flags & GI_FLAG_MSI_USED) ==
+ GI_FLAG_MSI_USED) {
+ found = false;
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+
+ /* Not enough interrupts were found */
+ if (!found || irq == sc->gic_mbi_end) {
+ mtx_unlock(&sc->gic_mbi_mtx);
+ return (ENXIO);
+ }
+
+ for (i = 0; i < count; i++) {
+ /* Mark the interrupt as used */
+ sc->gic_irqs[irq + i].gi_flags |= GI_FLAG_MSI_USED;
+ }
+ mtx_unlock(&sc->gic_mbi_mtx);
+
+ for (i = 0; i < count; i++)
+ srcs[i] = (struct intr_irqsrc *)&sc->gic_irqs[irq + i];
+ *pic = dev;
+
+ return (0);
+}
+
+static int
+gic_v3_release_msi(device_t dev, device_t child, int count,
+ struct intr_irqsrc **isrc)
+{
+ struct gic_v3_softc *sc;
+ struct gic_v3_irqsrc *gi;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ mtx_lock(&sc->gic_mbi_mtx);
+ for (i = 0; i < count; i++) {
+ gi = (struct gic_v3_irqsrc *)isrc[i];
+
+ KASSERT((gi->gi_flags & GI_FLAG_MSI_USED) == GI_FLAG_MSI_USED,
+ ("%s: Trying to release an unused MSI-X interrupt",
+ __func__));
+
+ gi->gi_flags &= ~GI_FLAG_MSI_USED;
+ }
+ mtx_unlock(&sc->gic_mbi_mtx);
+
+ return (0);
+}
+
+static int
+gic_v3_alloc_msix(device_t dev, device_t child, device_t *pic,
+ struct intr_irqsrc **isrcp)
+{
+ struct gic_v3_softc *sc;
+ int irq;
+
+ sc = device_get_softc(dev);
+
+ mtx_lock(&sc->gic_mbi_mtx);
+ /* Find an unused interrupt */
+ for (irq = sc->gic_mbi_start; irq < sc->gic_mbi_end; irq++) {
+ KASSERT((sc->gic_irqs[irq].gi_flags & GI_FLAG_MSI) != 0,
+ ("%s: Non-MSI interrupt found", __func__));
+ if ((sc->gic_irqs[irq].gi_flags & GI_FLAG_MSI_USED) == 0)
+ break;
+ }
+ /* No free interrupt was found */
+ if (irq == sc->gic_mbi_end) {
+ mtx_unlock(&sc->gic_mbi_mtx);
+ return (ENXIO);
+ }
+
+ /* Mark the interrupt as used */
+ sc->gic_irqs[irq].gi_flags |= GI_FLAG_MSI_USED;
+ mtx_unlock(&sc->gic_mbi_mtx);
+
+ *isrcp = (struct intr_irqsrc *)&sc->gic_irqs[irq];
+ *pic = dev;
+
+ return (0);
+}
+
+static int
+gic_v3_release_msix(device_t dev, device_t child, struct intr_irqsrc *isrc)
+{
+ struct gic_v3_softc *sc;
+ struct gic_v3_irqsrc *gi;
+
+ sc = device_get_softc(dev);
+ gi = (struct gic_v3_irqsrc *)isrc;
+
+ KASSERT((gi->gi_flags & GI_FLAG_MSI_USED) == GI_FLAG_MSI_USED,
+ ("%s: Trying to release an unused MSI-X interrupt", __func__));
+
+ mtx_lock(&sc->gic_mbi_mtx);
+ gi->gi_flags &= ~GI_FLAG_MSI_USED;
+ mtx_unlock(&sc->gic_mbi_mtx);
+
+ return (0);
+}
+
+static int
+gic_v3_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
+ uint64_t *addr, uint32_t *data)
+{
+ struct gic_v3_softc *sc = device_get_softc(dev);
+ struct gic_v3_irqsrc *gi = (struct gic_v3_irqsrc *)isrc;
+
+#define GICD_SETSPI_NSR 0x40
+ *addr = vtophys(rman_get_virtual(sc->gic_dist)) + GICD_SETSPI_NSR;
+ *data = gi->gi_irq;
+
+ return (0);
+}
diff --git a/sys/arm64/arm64/gic_v3_fdt.c b/sys/arm64/arm64/gic_v3_fdt.c
index c8a9615a8a5f..483d67ba6deb 100644
--- a/sys/arm64/arm64/gic_v3_fdt.c
+++ b/sys/arm64/arm64/gic_v3_fdt.c
@@ -121,6 +121,8 @@ gic_v3_fdt_attach(device_t dev)
pcell_t redist_regions;
intptr_t xref;
int err;
+ uint32_t *mbi_ranges;
+ ssize_t ret;
sc = device_get_softc(dev);
sc->dev = dev;
@@ -135,6 +137,21 @@ gic_v3_fdt_attach(device_t dev)
else
sc->gic_redists.nregions = redist_regions;
+ /* Add Message Based Interrupts using SPIs. */
+ ret = OF_getencprop_alloc_multi(ofw_bus_get_node(dev), "mbi-ranges",
+ sizeof(*mbi_ranges), (void **)&mbi_ranges);
+ if (ret > 0) {
+ if (ret % 2 == 0) {
+ /* Limit to a single range for now. */
+ sc->gic_mbi_start = mbi_ranges[0];
+ sc->gic_mbi_end = mbi_ranges[0] + mbi_ranges[1] - 1;
+ } else {
+ if (bootverbose)
+ device_printf(dev, "Malformed mbi-ranges property\n");
+ }
+ free(mbi_ranges, M_OFWPROP);
+ }
+
err = gic_v3_attach(dev);
if (err != 0)
goto error;
@@ -147,6 +164,9 @@ gic_v3_fdt_attach(device_t dev)
goto error;
}
+ if (sc->gic_mbi_start > 0)
+ intr_msi_register(dev, xref);
+
/* Register xref */
OF_device_register_xref(xref, dev);
diff --git a/sys/arm64/arm64/gic_v3_var.h b/sys/arm64/arm64/gic_v3_var.h
index f855e425d66d..1645c417fd8d 100644
--- a/sys/arm64/arm64/gic_v3_var.h
+++ b/sys/arm64/arm64/gic_v3_var.h
@@ -68,6 +68,11 @@ struct gic_v3_softc {
/* Re-Distributors */
struct gic_redists gic_redists;
+ /* Message Based Interrupts */
+ u_int gic_mbi_start;
+ u_int gic_mbi_end;
+ struct mtx gic_mbi_mtx;
+
uint32_t gic_pidr2;
u_int gic_bus;