git: 986bbba9a699 - main - arm/mv: Don't rely on firmware MSI mapping in ICU

Marcin Wojtas mw at FreeBSD.org
Tue Jul 20 21:28:39 UTC 2021


The branch main has been updated by mw:

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

commit 986bbba9a6994155ee651ce2df2f6fd9a67e13ea
Author:     Kornel Duleba <mindal at semihalf.com>
AuthorDate: 2021-02-17 15:28:46 +0000
Commit:     Marcin Wojtas <mw at FreeBSD.org>
CommitDate: 2021-07-20 21:24:42 +0000

    arm/mv: Don't rely on firmware MSI mapping in ICU
    
    On Armada8k boards various peripherals (e.g. USB) have interrupt lines
    connected to on of the ICU interrupt controllers.
    After an interrupt is detected it triggers MSI to a given address,
    with a programmed value. This in turn triggers an SPI interrupt.
    Normally MSI vector should be allocated by ICUs parent and set
    during interrupt allocation.
    Instead of doing that we relied on the ICU being pre-configured in firmware.
    This worked with EDK2 and older versions of U-Boot, but in the newer
    ones that is no longer the case.
    Extend ICU msi-parents - GICP and SEI to support MSI interface
    and use it during interrupt allocation.
    This allows us to boot on Armada 7k/8k SoCs independent from the
    firmware configuration and successfully use modern U-Boot + device tree.
    
    For SATA interrupts we need to apply a WA previously done in firmware.
    We have two SATA ports connected to one controller.
    Each ports gets its own interrupt, but only one of them is
    described in dts, also ahci_generic driver expects only one irq too.
    Fix it by mapping both interrupts to the same MSI when one of them
    is allocated, which allows us to use both SATA ports.
    
    Reviewed by: mmel, mw
    Obtained from: Semihalf
    Sponsored by: Marvell
    Differential Revision: https://reviews.freebsd.org/D28803
---
 sys/arm/mv/mv_ap806_gicp.c | 184 ++++++++++++++++++++++++++++++++++++++-------
 sys/arm/mv/mv_ap806_sei.c  | 104 ++++++++++++++++++++++++-
 sys/arm/mv/mv_cp110_icu.c  | 145 +++++++++++++++++++++++++++++++----
 3 files changed, 386 insertions(+), 47 deletions(-)

diff --git a/sys/arm/mv/mv_ap806_gicp.c b/sys/arm/mv/mv_ap806_gicp.c
index 0a1a69707956..ab0c540bf3f8 100644
--- a/sys/arm/mv/mv_ap806_gicp.c
+++ b/sys/arm/mv/mv_ap806_gicp.c
@@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/systm.h>
 #include <sys/bus.h>
 
+#include <sys/bitset.h>
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/rman.h>
@@ -49,10 +50,18 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 
+#include <arm/arm/gic_common.h>
+
+#include <dt-bindings/interrupt-controller/irq.h>
+
+#include "msi_if.h"
 #include "pic_if.h"
 
 #define	MV_AP806_GICP_MAX_NIRQS	207
 
+MALLOC_DECLARE(M_GICP);
+MALLOC_DEFINE(M_GICP, "gicp", "Marvell gicp driver");
+
 struct mv_ap806_gicp_softc {
 	device_t		dev;
 	device_t		parent;
@@ -61,6 +70,9 @@ struct mv_ap806_gicp_softc {
 	ssize_t			spi_ranges_cnt;
 	uint32_t		*spi_ranges;
 	struct intr_map_data_fdt *parent_map_data;
+
+	ssize_t			msi_bitmap_size; /* Nr of bits in the bitmap. */
+	BITSET_DEFINE_VAR()     *msi_bitmap;
 };
 
 static struct ofw_compat_data compat_data[] = {
@@ -71,6 +83,10 @@ static struct ofw_compat_data compat_data[] = {
 #define	RD4(sc, reg)		bus_read_4((sc)->res, (reg))
 #define	WR4(sc, reg, val)	bus_write_4((sc)->res, (reg), (val))
 
+static msi_alloc_msi_t mv_ap806_gicp_alloc_msi;
+static msi_release_msi_t mv_ap806_gicp_release_msi;
+static msi_map_msi_t mv_ap806_gicp_map_msi;
+
 static int
 mv_ap806_gicp_probe(device_t dev)
 {
@@ -90,6 +106,7 @@ mv_ap806_gicp_attach(device_t dev)
 {
 	struct mv_ap806_gicp_softc *sc;
 	phandle_t node, xref, intr_parent;
+	int i, rid;
 
 	sc = device_get_softc(dev);
 	sc->dev = dev;
@@ -107,9 +124,28 @@ mv_ap806_gicp_attach(device_t dev)
 		return (ENXIO);
 	}
 
+	rid = 0;
+	sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
+	if (sc->res == NULL) {
+		device_printf(dev, "cannot allocate resources for device\n");
+		return (ENXIO);
+        }
+
 	sc->spi_ranges_cnt = OF_getencprop_alloc_multi(node, "marvell,spi-ranges",
 	    sizeof(*sc->spi_ranges), (void **)&sc->spi_ranges);
 
+	sc->msi_bitmap_size = 0;
+	for (i = 0; i < sc->spi_ranges_cnt; i += 2)
+		sc->msi_bitmap_size += sc->spi_ranges[i + 1];
+
+	/*
+	 * Create a bitmap of all MSIs that we have.
+	 * Each has a correspoding SPI in the GIC.
+	 * It will be used to dynamically allocate IRQs when requested.
+	 */
+	sc->msi_bitmap = BITSET_ALLOC(sc->msi_bitmap_size, M_GICP, M_WAITOK);
+	BIT_FILL(sc->msi_bitmap_size, sc->msi_bitmap);	/* 1 - available, 0 - used. */
+
 	xref = OF_xref_from_node(node);
 	if (intr_pic_register(dev, xref) == NULL) {
 		device_printf(dev, "Cannot register GICP\n");
@@ -131,38 +167,58 @@ mv_ap806_gicp_detach(device_t dev)
 	return (EBUSY);
 }
 
+static uint32_t
+mv_ap806_gicp_msi_to_spi(struct mv_ap806_gicp_softc *sc, int irq)
+{
+	int i;
+
+	for (i = 0; i < sc->spi_ranges_cnt; i += 2) {
+		if (irq < sc->spi_ranges[i + 1]) {
+			irq += sc->spi_ranges[i];
+			break;
+		}
+		irq -= sc->spi_ranges[i + 1];
+	}
+
+	return (irq - GIC_FIRST_SPI);
+}
+
+static uint32_t
+mv_ap806_gicp_irq_to_msi(struct mv_ap806_gicp_softc *sc, int irq)
+{
+	int i;
+
+	for (i = 0; i < sc->spi_ranges_cnt; i += 2) {
+		if (irq >= sc->spi_ranges[i] &&
+		    irq - sc->spi_ranges[i] < sc->spi_ranges[i + 1]) {
+			irq -= sc->spi_ranges[i];
+			break;
+		}
+	}
+
+	return (irq);
+}
+
 static struct intr_map_data *
 mv_ap806_gicp_convert_map_data(struct mv_ap806_gicp_softc *sc,
     struct intr_map_data *data)
 {
 	struct intr_map_data_fdt *daf;
-	uint32_t i, irq_num, irq_type;
+	uint32_t irq_num;
 
 	daf = (struct intr_map_data_fdt *)data;
 	if (daf->ncells != 2)
 		return (NULL);
 
 	irq_num = daf->cells[0];
-	irq_type = daf->cells[1];
 	if (irq_num >= MV_AP806_GICP_MAX_NIRQS)
 		return (NULL);
 
 	/* Construct GIC compatible mapping. */
 	sc->parent_map_data->ncells = 3;
 	sc->parent_map_data->cells[0] = 0; /* SPI */
-	sc->parent_map_data->cells[2] = irq_type;
-
-	/* Map the interrupt number to SPI number */
-	for (i = 0; i < sc->spi_ranges_cnt; i += 2) {
-		if (irq_num < sc->spi_ranges[i + 1]) {
-			irq_num += sc->spi_ranges[i];
-			break;
-		}
-
-		irq_num -= sc->spi_ranges[i];
-	}
-
-	sc->parent_map_data->cells[1] = irq_num - 32;
+	sc->parent_map_data->cells[1] = mv_ap806_gicp_msi_to_spi(sc, irq_num);
+	sc->parent_map_data->cells[2] = IRQ_TYPE_LEVEL_HIGH;
 
 	return ((struct intr_map_data *)sc->parent_map_data);
 }
@@ -205,21 +261,9 @@ static int
 mv_ap806_gicp_map_intr(device_t dev, struct intr_map_data *data,
     struct intr_irqsrc **isrcp)
 {
-	struct mv_ap806_gicp_softc *sc;
-	int ret;
-
-	sc = device_get_softc(dev);
-
-	if (data->type != INTR_MAP_DATA_FDT)
-		return (ENOTSUP);
 
-	data = mv_ap806_gicp_convert_map_data(sc, data);
-	if (data == NULL)
-		return (EINVAL);
-
-	ret = PIC_MAP_INTR(sc->parent, data, isrcp);
-	(*isrcp)->isrc_dev = sc->dev;
-	return(ret);
+	panic("%s: MSI interface has to be used to map an interrupt.\n",
+	    __func__);
 }
 
 static int
@@ -295,6 +339,83 @@ mv_ap806_gicp_post_filter(device_t dev, struct intr_irqsrc *isrc)
 	PIC_POST_FILTER(sc->parent, isrc);
 }
 
+static int
+mv_ap806_gicp_alloc_msi(device_t dev, device_t child, int count, int maxcount,
+    device_t *pic, struct intr_irqsrc **srcs)
+{
+	struct mv_ap806_gicp_softc *sc;
+	int i, ret, vector;
+
+	sc = device_get_softc(dev);
+
+	for (i = 0; i < count; i++) {
+		/*
+		 * Find first available vector represented by first set bit
+		 * in the bitmap. BIT_FFS starts the count from 1, 0 means
+		 * that nothing was found.
+		 */
+		vector = BIT_FFS(sc->msi_bitmap_size, sc->msi_bitmap);
+		if (vector == 0) {
+			ret = ENOMEM;
+			i--;
+			goto fail;
+		}
+		vector--;
+		BIT_CLR(sc->msi_bitmap_size, vector, sc->msi_bitmap);
+
+		/* Create GIC compatible SPI interrupt description. */
+		sc->parent_map_data->ncells = 3;
+		sc->parent_map_data->cells[0] = 0;	/* SPI */
+		sc->parent_map_data->cells[1] = mv_ap806_gicp_msi_to_spi(sc, vector);
+		sc->parent_map_data->cells[2] = IRQ_TYPE_LEVEL_HIGH;
+
+		ret = PIC_MAP_INTR(sc->parent,
+		    (struct intr_map_data *)sc->parent_map_data,
+		    &srcs[i]);
+		if (ret != 0)
+			goto fail;
+
+		srcs[i]->isrc_dev = dev;
+	}
+
+	return (0);
+fail:
+	mv_ap806_gicp_release_msi(dev, child, i + 1, srcs);
+	return (ret);
+}
+
+static int
+mv_ap806_gicp_release_msi(device_t dev, device_t child, int count,
+    struct intr_irqsrc **srcs)
+{
+	struct mv_ap806_gicp_softc *sc;
+	int i;
+
+	sc = device_get_softc(dev);
+
+	for (i = 0; i < count; i++) {
+		BIT_SET(sc->msi_bitmap_size,
+		    mv_ap806_gicp_irq_to_msi(sc, srcs[i]->isrc_irq),
+		    sc->msi_bitmap);
+	}
+
+	return (0);
+}
+
+static int
+mv_ap806_gicp_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
+    uint64_t *addr, uint32_t *data)
+{
+	struct mv_ap806_gicp_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	*addr = rman_get_start(sc->res);
+	*data = mv_ap806_gicp_irq_to_msi(sc, isrc->isrc_irq);
+
+	return (0);
+}
+
 static device_method_t mv_ap806_gicp_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe,		mv_ap806_gicp_probe),
@@ -313,6 +434,11 @@ static device_method_t mv_ap806_gicp_methods[] = {
 	DEVMETHOD(pic_post_ithread,	mv_ap806_gicp_post_ithread),
 	DEVMETHOD(pic_pre_ithread,	mv_ap806_gicp_pre_ithread),
 
+	/* MSI interface */
+	DEVMETHOD(msi_alloc_msi,	mv_ap806_gicp_alloc_msi),
+	DEVMETHOD(msi_release_msi,	mv_ap806_gicp_release_msi),
+	DEVMETHOD(msi_map_msi,		mv_ap806_gicp_map_msi),
+
 	DEVMETHOD_END
 };
 
diff --git a/sys/arm/mv/mv_ap806_sei.c b/sys/arm/mv/mv_ap806_sei.c
index 5022e6765d0a..fad57dcad17f 100644
--- a/sys/arm/mv/mv_ap806_sei.c
+++ b/sys/arm/mv/mv_ap806_sei.c
@@ -32,6 +32,8 @@ __FBSDID("$FreeBSD$");
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/bus.h>
+
+#include <sys/bitset.h>
 #include <sys/kernel.h>
 #include <sys/proc.h>
 #include <sys/rman.h>
@@ -48,6 +50,7 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 
+#include "msi_if.h"
 #include "pic_if.h"
 
 #define	MV_AP806_SEI_LOCK(_sc)		mtx_lock(&(_sc)->mtx)
@@ -58,7 +61,6 @@ __FBSDID("$FreeBSD$");
 #define	MV_AP806_SEI_ASSERT_LOCKED(_sc)	mtx_assert(&_sc->mtx, MA_OWNED);
 #define	MV_AP806_SEI_ASSERT_UNLOCKED(_sc) mtx_assert(&_sc->mtx, MA_NOTOWNED);
 
-#define	MV_AP806_SEI_MAX_NIRQS	64
 #define GICP_SECR0		0x00
 #define GICP_SECR1		0x04
 #define GICP_SECR(i)		(0x00  + (((i)/32) * 0x4))
@@ -68,6 +70,16 @@ __FBSDID("$FreeBSD$");
 #define GICP_SEMR(i)		(0x20  + (((i)/32) * 0x4))
 #define GICP_SEMR_BIT(i)	((i) % 32)
 
+#define	MV_AP806_SEI_AP_FIRST	0
+#define	MV_AP806_SEI_AP_SIZE	21
+#define	MV_AP806_SEI_CP_FIRST	21
+#define	MV_AP806_SEI_CP_SIZE	43
+#define	MV_AP806_SEI_MAX_NIRQS	(MV_AP806_SEI_AP_SIZE + MV_AP806_SEI_CP_SIZE)
+
+#define	MV_AP806_SEI_SETSPI_OFFSET	0x30
+
+BITSET_DEFINE(sei_msi_bitmap, MV_AP806_SEI_CP_SIZE);
+
 struct mv_ap806_sei_irqsrc {
 	struct intr_irqsrc	isrc;
 	u_int			irq;
@@ -81,6 +93,8 @@ struct mv_ap806_sei_softc {
 	struct mtx		mtx;
 
 	struct mv_ap806_sei_irqsrc *isrcs;
+
+	struct sei_msi_bitmap	msi_bitmap;
 };
 
 static struct ofw_compat_data compat_data[] = {
@@ -91,6 +105,10 @@ static struct ofw_compat_data compat_data[] = {
 #define	RD4(sc, reg)		bus_read_4((sc)->mem_res, (reg))
 #define	WR4(sc, reg, val)	bus_write_4((sc)->mem_res, (reg), (val))
 
+static msi_alloc_msi_t mv_ap806_sei_alloc_msi;
+static msi_release_msi_t mv_ap806_sei_release_msi;
+static msi_map_msi_t mv_ap806_sei_map_msi;
+
 static inline void
 mv_ap806_sei_isrc_mask(struct mv_ap806_sei_softc *sc,
      struct mv_ap806_sei_irqsrc *sisrc, uint32_t val)
@@ -152,8 +170,13 @@ mv_ap806_sei_map(device_t dev, struct intr_map_data *data, u_int *irqp)
 		return (ENOTSUP);
 
 	daf = (struct intr_map_data_fdt *)data;
-	if (daf->ncells != 1 || daf->cells[0] >= MV_AP806_SEI_MAX_NIRQS)
+	if (daf->ncells != 1)
 		return (EINVAL);
+
+	if (daf->cells[0] < MV_AP806_SEI_AP_FIRST ||
+	    daf->cells[0] >= MV_AP806_SEI_AP_FIRST + MV_AP806_SEI_AP_SIZE)
+		return (EINVAL);
+
 	irq = daf->cells[0];
 	if (irqp != NULL)
 		*irqp = irq;
@@ -361,6 +384,12 @@ mv_ap806_sei_attach(device_t dev)
 		goto fail;
 	}
 
+	/*
+	 * Bitmap of all IRQs.
+	 * 1 - available, 0 - used.
+	 */
+	BIT_FILL(MV_AP806_SEI_CP_SIZE, &sc->msi_bitmap);
+
 	OF_device_register_xref(xref, dev);
 	return (0);
 
@@ -382,6 +411,72 @@ mv_ap806_sei_detach(device_t dev)
 	return (EBUSY);
 }
 
+static int
+mv_ap806_sei_alloc_msi(device_t dev, device_t child, int count, int maxcount,
+    device_t *pic, struct intr_irqsrc **srcs)
+{
+	struct mv_ap806_sei_softc *sc;
+	int i, ret = 0, vector;
+
+	sc = device_get_softc(dev);
+
+	for (i = 0; i < count; i++) {
+		/*
+		 * Find first available MSI vector represented by first set bit
+		 * in the bitmap. BIT_FFS starts the count from 1,
+		 * 0 means that nothing was found.
+		 */
+		vector = BIT_FFS_AT(MV_AP806_SEI_CP_SIZE, &sc->msi_bitmap, 0);
+		if (vector == 0) {
+			ret = ENOMEM;
+			i--;
+			goto fail;
+		}
+
+		vector--;
+		BIT_CLR(MV_AP806_SEI_CP_SIZE, vector, &sc->msi_bitmap);
+		vector += MV_AP806_SEI_CP_FIRST;
+
+		srcs[i] = &sc->isrcs[vector].isrc;
+	}
+
+	return (ret);
+fail:
+	mv_ap806_sei_release_msi(dev, child, i + 1, srcs);
+	return (ret);
+}
+
+static int
+mv_ap806_sei_release_msi(device_t dev, device_t child, int count, struct intr_irqsrc **srcs)
+{
+	struct mv_ap806_sei_softc *sc;
+	int i;
+
+	sc = device_get_softc(dev);
+
+	for (i = 0; i < count; i++) {
+		BIT_SET(MV_AP806_SEI_CP_SIZE,
+		    srcs[i]->isrc_irq - MV_AP806_SEI_CP_FIRST,
+		    &sc->msi_bitmap);
+	}
+
+	return (0);
+}
+
+static int
+mv_ap806_sei_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
+    uint64_t *addr, uint32_t *data)
+{
+	struct mv_ap806_sei_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	*addr = rman_get_start(sc->mem_res) + MV_AP806_SEI_SETSPI_OFFSET;
+	*data = isrc->isrc_irq;
+
+	return (0);
+}
+
 static device_method_t mv_ap806_sei_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe,		mv_ap806_sei_probe),
@@ -398,6 +493,11 @@ static device_method_t mv_ap806_sei_methods[] = {
 	DEVMETHOD(pic_post_ithread,	mv_ap806_sei_post_ithread),
 	DEVMETHOD(pic_pre_ithread,	mv_ap806_sei_pre_ithread),
 
+	/* MSI interface */
+	DEVMETHOD(msi_alloc_msi,	mv_ap806_sei_alloc_msi),
+	DEVMETHOD(msi_release_msi,	mv_ap806_sei_release_msi),
+	DEVMETHOD(msi_map_msi,		mv_ap806_sei_map_msi),
+
 	DEVMETHOD_END
 };
 
diff --git a/sys/arm/mv/mv_cp110_icu.c b/sys/arm/mv/mv_cp110_icu.c
index 12dd6989e339..c783043ab1df 100644
--- a/sys/arm/mv/mv_cp110_icu.c
+++ b/sys/arm/mv/mv_cp110_icu.c
@@ -50,7 +50,12 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus_subr.h>
 
 #include <dt-bindings/interrupt-controller/irq.h>
+
 #include "pic_if.h"
+#include "msi_if.h"
+
+#define	ICU_TYPE_NSR		1
+#define	ICU_TYPE_SEI		2
 
 #define	ICU_GRP_NSR		0x0
 #define	ICU_GRP_SR		0x1
@@ -61,19 +66,28 @@ __FBSDID("$FreeBSD$");
 #define	ICU_SETSPI_NSR_AH	0x14
 #define	ICU_CLRSPI_NSR_AL	0x18
 #define	ICU_CLRSPI_NSR_AH	0x1c
+#define	ICU_SETSPI_SEI_AL	0x50
+#define	ICU_SETSPI_SEI_AH	0x54
 #define	ICU_INT_CFG(x)	(0x100 + (x) * 4)
 #define	 ICU_INT_ENABLE		(1 << 24)
 #define	 ICU_INT_EDGE		(1 << 28)
 #define	 ICU_INT_GROUP_SHIFT	29
 #define	 ICU_INT_MASK		0x3ff
 
+#define	ICU_INT_SATA0		109
+#define	ICU_INT_SATA1		107
+
 #define	MV_CP110_ICU_MAX_NIRQS	207
 
+#define	MV_CP110_ICU_CLRSPI_OFFSET	0x8
+
 struct mv_cp110_icu_softc {
 	device_t		dev;
 	device_t		parent;
 	struct resource		*res;
 	struct intr_map_data_fdt *parent_map_data;
+	bool			initialized;
+	int			type;
 };
 
 static struct resource_spec mv_cp110_icu_res_spec[] = {
@@ -82,8 +96,8 @@ static struct resource_spec mv_cp110_icu_res_spec[] = {
 };
 
 static struct ofw_compat_data compat_data[] = {
-	{"marvell,cp110-icu-nsr",	1},
-	{"marvell,cp110-icu-sei",	2},
+	{"marvell,cp110-icu-nsr",	ICU_TYPE_NSR},
+	{"marvell,cp110-icu-sei",	ICU_TYPE_SEI},
 	{NULL,				0}
 };
 
@@ -109,10 +123,14 @@ mv_cp110_icu_attach(device_t dev)
 {
 	struct mv_cp110_icu_softc *sc;
 	phandle_t node, msi_parent;
+	uint32_t reg, icu_grp;
+	int i;
 
 	sc = device_get_softc(dev);
 	sc->dev = dev;
 	node = ofw_bus_get_node(dev);
+	sc->type = (int)ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+	sc->initialized = false;
 
 	if (OF_getencprop(node, "msi-parent", &msi_parent,
 	    sizeof(phandle_t)) <= 0) {
@@ -134,10 +152,20 @@ mv_cp110_icu_attach(device_t dev)
 		goto fail;
 	}
 
-	/* Allocate GICP compatible mapping entry (2 cells) */
+	/* Allocate GICP/SEI compatible mapping entry (2 cells) */
 	sc->parent_map_data = (struct intr_map_data_fdt *)intr_alloc_map_data(
 	    INTR_MAP_DATA_FDT, sizeof(struct intr_map_data_fdt) +
 	    + 3 * sizeof(phandle_t), M_WAITOK | M_ZERO);
+
+	/* Clear any previous mapping done by firmware. */
+	for (i = 0; i < MV_CP110_ICU_MAX_NIRQS; i++) {
+		reg = RD4(sc, ICU_INT_CFG(i));
+		icu_grp = reg >> ICU_INT_GROUP_SHIFT;
+
+		if (icu_grp == ICU_GRP_NSR || icu_grp == ICU_GRP_SEI)
+			WR4(sc, ICU_INT_CFG(i), 0);
+	}
+
 	return (0);
 
 fail:
@@ -154,15 +182,17 @@ mv_cp110_icu_convert_map_data(struct mv_cp110_icu_softc *sc, struct intr_map_dat
 	daf = (struct intr_map_data_fdt *)data;
 	if (daf->ncells != 2)
 		return (NULL);
+
 	irq_no = daf->cells[0];
-	irq_type = daf->cells[1];
 	if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
 		return (NULL);
+
+	irq_type = daf->cells[1];
 	if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
 	    irq_type != IRQ_TYPE_EDGE_RISING)
 		return (NULL);
 
-	/* We rely on fact that ICU->GIC mapping is preset by bootstrap. */
+	/* ICU -> GICP/SEI mapping is set in mv_cp110_icu_map_intr. */
 	reg = RD4(sc, ICU_INT_CFG(irq_no));
 
 	/* Construct GICP compatible mapping. */
@@ -212,13 +242,40 @@ mv_cp110_icu_disable_intr(device_t dev, struct intr_irqsrc *isrc)
 	PIC_DISABLE_INTR(sc->parent, isrc);
 }
 
+static void
+mv_cp110_icu_init(struct mv_cp110_icu_softc *sc, uint64_t addr)
+{
+
+	if (sc->initialized)
+		return;
+
+	switch (sc->type) {
+	case ICU_TYPE_NSR:
+		WR4(sc, ICU_SETSPI_NSR_AL, addr & UINT32_MAX);
+		WR4(sc, ICU_SETSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
+		addr += MV_CP110_ICU_CLRSPI_OFFSET;
+		WR4(sc, ICU_CLRSPI_NSR_AL, addr & UINT32_MAX);
+		WR4(sc, ICU_CLRSPI_NSR_AH, (addr >> 32) & UINT32_MAX);
+		break;
+	case ICU_TYPE_SEI:
+		WR4(sc, ICU_SETSPI_SEI_AL, addr & UINT32_MAX);
+		WR4(sc, ICU_SETSPI_SEI_AH, (addr >> 32) & UINT32_MAX);
+		break;
+	default:
+		panic("Unkown ICU type.");
+	}
+
+	sc->initialized = true;
+}
+
 static int
 mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data,
     struct intr_irqsrc **isrcp)
 {
 	struct mv_cp110_icu_softc *sc;
 	struct intr_map_data_fdt *daf;
-	uint32_t reg, irq_no, irq_type;
+	uint32_t vector, irq_no, irq_type;
+	uint64_t addr;
 	int ret;
 
 	sc = device_get_softc(dev);
@@ -230,23 +287,62 @@ mv_cp110_icu_map_intr(device_t dev, struct intr_map_data *data,
 	daf = (struct intr_map_data_fdt *)data;
 	if (daf->ncells != 2)
 		return (EINVAL);
+
 	irq_no = daf->cells[0];
+	if (irq_no >= MV_CP110_ICU_MAX_NIRQS)
+		return (EINVAL);
+
 	irq_type = daf->cells[1];
-	data = mv_cp110_icu_convert_map_data(sc, data);
-	if (data == NULL)
+	if (irq_type != IRQ_TYPE_LEVEL_HIGH &&
+	    irq_type != IRQ_TYPE_EDGE_RISING)
 		return (EINVAL);
 
-	reg = RD4(sc, ICU_INT_CFG(irq_no));
-	reg |= ICU_INT_ENABLE;
-	if (irq_type == IRQ_TYPE_LEVEL_HIGH)
-		reg &= ~ICU_INT_EDGE;
+	/*
+	 * Allocate MSI vector.
+	 * We don't use intr_alloc_msi wrapper, since it registers a new irq
+	 * in the kernel. In our case irq was already added by the ofw code.
+	 */
+	ret = MSI_ALLOC_MSI(sc->parent, dev, 1, 1, NULL, isrcp);
+	if (ret != 0)
+		return (ret);
+
+	ret = MSI_MAP_MSI(sc->parent, dev, *isrcp, &addr, &vector);
+	if (ret != 0)
+		goto fail;
+
+	mv_cp110_icu_init(sc, addr);
+	vector |= ICU_INT_ENABLE;
+
+	if (sc->type == ICU_TYPE_NSR)
+		vector |= ICU_GRP_NSR << ICU_INT_GROUP_SHIFT;
 	else
-		reg |= ICU_INT_EDGE;
-	WR4(sc, ICU_INT_CFG(irq_no), reg);
+		vector |= ICU_GRP_SEI << ICU_INT_GROUP_SHIFT;
+
+	if (irq_type & IRQ_TYPE_EDGE_BOTH)
+		vector |= ICU_INT_EDGE;
+
+	WR4(sc, ICU_INT_CFG(irq_no), vector);
+
+	/*
+	 * SATA controller has two ports, each gets its own interrupt.
+	 * The problem is that only one irq is described in dts.
+	 * Also ahci_generic driver supports only one irq per controller.
+	 * As a workaround map both interrupts when one of them is allocated.
+	 * This allows us to use both SATA ports.
+	 */
+	if (irq_no == ICU_INT_SATA0)
+		WR4(sc, ICU_INT_CFG(ICU_INT_SATA1), vector);
+	if (irq_no == ICU_INT_SATA1)
+		WR4(sc, ICU_INT_CFG(ICU_INT_SATA0), vector);
 
-	ret = PIC_MAP_INTR(sc->parent, data, isrcp);
 	(*isrcp)->isrc_dev = sc->dev;
 	return (ret);
+
+fail:
+	if (*isrcp != NULL)
+		MSI_RELEASE_MSI(sc->parent, dev, 1, isrcp);
+
+	return (ret);
 }
 
 static int
@@ -254,13 +350,30 @@ mv_cp110_icu_deactivate_intr(device_t dev, struct intr_irqsrc *isrc,
     struct resource *res, struct intr_map_data *data)
 {
 	struct mv_cp110_icu_softc *sc;
+	struct intr_map_data_fdt *daf;
+	int irq_no, ret;
+
+	if (data->type != INTR_MAP_DATA_FDT)
+		return (ENOTSUP);
 
 	sc = device_get_softc(dev);
+	daf = (struct intr_map_data_fdt *)data;
+	if (daf->ncells != 2)
+		return (EINVAL);
+
+	irq_no = daf->cells[0];
 	data = mv_cp110_icu_convert_map_data(sc, data);
 	if (data == NULL)
 		return (EINVAL);
 
-	return (PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data));
+	/* Clear the mapping. */
+	WR4(sc, ICU_INT_CFG(irq_no), 0);
+
+	ret = PIC_DEACTIVATE_INTR(sc->parent, isrc, res, data);
+	if (ret != 0)
+		return (ret);
+
+	return (MSI_RELEASE_MSI(sc->parent, dev, 1, &isrc));
 }
 
 static int


More information about the dev-commits-src-main mailing list