Date: Tue, 20 Jul 2021 21:28:39 GMT From: Marcin Wojtas <mw@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 986bbba9a699 - main - arm/mv: Don't rely on firmware MSI mapping in ICU Message-ID: <202107202128.16KLSd1M068971@gitrepo.freebsd.org>
next in thread | raw e-mail | index | archive | help
The branch main has been updated by mw: URL: https://cgit.FreeBSD.org/src/commit/?id=986bbba9a6994155ee651ce2df2f6fd9a67e13ea commit 986bbba9a6994155ee651ce2df2f6fd9a67e13ea Author: Kornel Duleba <mindal@semihalf.com> AuthorDate: 2021-02-17 15:28:46 +0000 Commit: Marcin Wojtas <mw@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
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202107202128.16KLSd1M068971>