Support for MSI-X on Annapurna Alpine
This patch adds support for MSI-X interrupts on Annapurna Alpine platform. MSI-X on Alpine work similarly to GICv2m, i.e. some range of SPI interrupts is reserved in GIC and individual SPIs can be triggered by MSI-X messages. This SPI range is defined in FDT. Obtained from: Semihalf Submitted by: Michal Stanek <mst@semihalf.com> Sponsored by: Annapurna Labs Reviewed by: nwhitehorn, wma Differential Revision: https://reviews.freebsd.org/D7579
This commit is contained in:
parent
6ed982a221
commit
be48125bbf
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=307668
394
sys/arm/annapurna/alpine/alpine_pci_msix.c
Normal file
394
sys/arm/annapurna/alpine/alpine_pci_msix.c
Normal file
@ -0,0 +1,394 @@
|
||||
/*-
|
||||
* Copyright (c) 2015,2016 Annapurna Labs Ltd. and affiliates
|
||||
* All rights reserved.
|
||||
*
|
||||
* Developed by Semihalf.
|
||||
*
|
||||
* 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/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/mutex.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/rman.h>
|
||||
#include <sys/vmem.h>
|
||||
|
||||
#include <dev/ofw/ofw_bus.h>
|
||||
#include <dev/ofw/ofw_bus_subr.h>
|
||||
|
||||
#include "msi_if.h"
|
||||
#include "pic_if.h"
|
||||
|
||||
#define AL_SPI_INTR 0
|
||||
#define AL_EDGE_HIGH 1
|
||||
#define ERR_NOT_IN_MAP -1
|
||||
#define IRQ_OFFSET 1
|
||||
#define GIC_INTR_CELL_CNT 3
|
||||
#define INTR_RANGE_COUNT 2
|
||||
#define MAX_MSIX_COUNT 160
|
||||
|
||||
static int al_msix_attach(device_t);
|
||||
static int al_msix_probe(device_t);
|
||||
|
||||
static msi_alloc_msi_t al_msix_alloc_msi;
|
||||
static msi_release_msi_t al_msix_release_msi;
|
||||
static msi_alloc_msix_t al_msix_alloc_msix;
|
||||
static msi_release_msix_t al_msix_release_msix;
|
||||
static msi_map_msi_t al_msix_map_msi;
|
||||
|
||||
static int al_find_intr_pos_in_map(device_t, struct intr_irqsrc *);
|
||||
|
||||
static struct ofw_compat_data compat_data[] = {
|
||||
{"annapurna-labs,al-msix", true},
|
||||
{"annapurna-labs,alpine-msix", true},
|
||||
{NULL, false}
|
||||
};
|
||||
|
||||
/*
|
||||
* Bus interface definitions.
|
||||
*/
|
||||
static device_method_t al_msix_methods[] = {
|
||||
DEVMETHOD(device_probe, al_msix_probe),
|
||||
DEVMETHOD(device_attach, al_msix_attach),
|
||||
|
||||
/* Interrupt controller interface */
|
||||
DEVMETHOD(msi_alloc_msi, al_msix_alloc_msi),
|
||||
DEVMETHOD(msi_release_msi, al_msix_release_msi),
|
||||
DEVMETHOD(msi_alloc_msix, al_msix_alloc_msix),
|
||||
DEVMETHOD(msi_release_msix, al_msix_release_msix),
|
||||
DEVMETHOD(msi_map_msi, al_msix_map_msi),
|
||||
|
||||
DEVMETHOD_END
|
||||
};
|
||||
|
||||
struct al_msix_softc {
|
||||
bus_addr_t base_addr;
|
||||
struct resource *res;
|
||||
uint32_t irq_min;
|
||||
uint32_t irq_max;
|
||||
uint32_t irq_count;
|
||||
struct mtx msi_mtx;
|
||||
vmem_t *irq_alloc;
|
||||
device_t gic_dev;
|
||||
/* Table of isrcs maps isrc pointer to vmem_alloc'd irq number */
|
||||
struct intr_irqsrc *isrcs[MAX_MSIX_COUNT];
|
||||
};
|
||||
|
||||
static driver_t al_msix_driver = {
|
||||
"al_msix",
|
||||
al_msix_methods,
|
||||
sizeof(struct al_msix_softc),
|
||||
};
|
||||
|
||||
devclass_t al_msix_devclass;
|
||||
|
||||
DRIVER_MODULE(al_msix, ofwbus, al_msix_driver, al_msix_devclass, 0, 0);
|
||||
DRIVER_MODULE(al_msix, simplebus, al_msix_driver, al_msix_devclass, 0, 0);
|
||||
|
||||
MALLOC_DECLARE(M_AL_MSIX);
|
||||
MALLOC_DEFINE(M_AL_MSIX, "al_msix", "Alpine MSIX");
|
||||
|
||||
static int
|
||||
al_msix_probe(device_t dev)
|
||||
{
|
||||
|
||||
if (!ofw_bus_status_okay(dev))
|
||||
return (ENXIO);
|
||||
|
||||
if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
|
||||
return (ENXIO);
|
||||
|
||||
device_set_desc(dev, "Annapurna-Labs MSI-X Controller");
|
||||
return (BUS_PROBE_DEFAULT);
|
||||
}
|
||||
|
||||
static int
|
||||
al_msix_attach(device_t dev)
|
||||
{
|
||||
struct al_msix_softc *sc;
|
||||
device_t gic_dev;
|
||||
phandle_t iparent;
|
||||
phandle_t node;
|
||||
intptr_t xref;
|
||||
int interrupts[INTR_RANGE_COUNT];
|
||||
int nintr, i, rid;
|
||||
uint32_t icells, *intr;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
node = ofw_bus_get_node(dev);
|
||||
xref = OF_xref_from_node(node);
|
||||
OF_device_register_xref(xref, dev);
|
||||
|
||||
rid = 0;
|
||||
sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
|
||||
if (sc->res == NULL) {
|
||||
device_printf(dev, "Failed to allocate resource\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
sc->base_addr = (bus_addr_t)rman_get_start(sc->res);
|
||||
|
||||
/* Register this device to handle MSI interrupts */
|
||||
if (intr_msi_register(dev, xref) != 0) {
|
||||
device_printf(dev, "could not register MSI-X controller\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
else
|
||||
device_printf(dev, "MSI-X controller registered\n");
|
||||
|
||||
/* Find root interrupt controller */
|
||||
iparent = ofw_bus_find_iparent(node);
|
||||
if (iparent == 0) {
|
||||
device_printf(dev, "No interrupt-parrent found. "
|
||||
"Error in DTB\n");
|
||||
return (ENXIO);
|
||||
} else {
|
||||
/* While at parent - store interrupt cells prop */
|
||||
if (OF_searchencprop(OF_node_from_xref(iparent),
|
||||
"#interrupt-cells", &icells, sizeof(icells)) == -1) {
|
||||
device_printf(dev, "DTB: Missing #interrupt-cells "
|
||||
"property in GIC node\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
}
|
||||
|
||||
gic_dev = OF_device_from_xref(iparent);
|
||||
if (gic_dev == NULL) {
|
||||
device_printf(dev, "Cannot find GIC device\n");
|
||||
return (ENXIO);
|
||||
}
|
||||
sc->gic_dev = gic_dev;
|
||||
|
||||
/* Manually read range of interrupts from DTB */
|
||||
nintr = OF_getencprop_alloc(node, "interrupts", sizeof(*intr),
|
||||
(void **)&intr);
|
||||
if (nintr == 0) {
|
||||
device_printf(dev, "Cannot read interrupts prop from DTB\n");
|
||||
return (ENXIO);
|
||||
} else if ((nintr / icells) != INTR_RANGE_COUNT) {
|
||||
/* Supposed to have min and max value only */
|
||||
device_printf(dev, "Unexpected count of interrupts "
|
||||
"in DTB node\n");
|
||||
return (EINVAL);
|
||||
}
|
||||
|
||||
/* Read interrupt range values */
|
||||
for (i = 0; i < INTR_RANGE_COUNT; i++)
|
||||
interrupts[i] = intr[(i * icells) + IRQ_OFFSET];
|
||||
|
||||
sc->irq_min = interrupts[0];
|
||||
sc->irq_max = interrupts[1];
|
||||
sc->irq_count = (sc->irq_max - sc->irq_min + 1);
|
||||
|
||||
if (sc->irq_count > MAX_MSIX_COUNT) {
|
||||
device_printf(dev, "Available MSI-X count exceeds buffer size."
|
||||
" Capping to %d\n", MAX_MSIX_COUNT);
|
||||
sc->irq_count = MAX_MSIX_COUNT;
|
||||
}
|
||||
|
||||
mtx_init(&sc->msi_mtx, "msi_mtx", NULL, MTX_DEF);
|
||||
|
||||
sc->irq_alloc = vmem_create("Alpine MSI-X IRQs", 0, sc->irq_count,
|
||||
1, 0, M_FIRSTFIT | M_WAITOK);
|
||||
|
||||
device_printf(dev, "MSI-X SPI IRQ %d-%d\n", sc->irq_min, sc->irq_max);
|
||||
|
||||
return (bus_generic_attach(dev));
|
||||
}
|
||||
|
||||
static int
|
||||
al_find_intr_pos_in_map(device_t dev, struct intr_irqsrc *isrc)
|
||||
{
|
||||
struct al_msix_softc *sc;
|
||||
int i;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
for (i = 0; i < MAX_MSIX_COUNT; i++)
|
||||
if (sc->isrcs[i] == isrc)
|
||||
return (i);
|
||||
return (ERR_NOT_IN_MAP);
|
||||
}
|
||||
|
||||
static int
|
||||
al_msix_map_msi(device_t dev, device_t child, struct intr_irqsrc *isrc,
|
||||
uint64_t *addr, uint32_t *data)
|
||||
{
|
||||
struct al_msix_softc *sc;
|
||||
int i, spi;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
i = al_find_intr_pos_in_map(dev, isrc);
|
||||
if (i == ERR_NOT_IN_MAP)
|
||||
return (EINVAL);
|
||||
|
||||
spi = sc->irq_min + i;
|
||||
|
||||
/*
|
||||
* MSIX message address format:
|
||||
* [63:20] - MSIx TBAR
|
||||
* Same value as the MSIx Translation Base Address Register
|
||||
* [19] - WFE_EXIT
|
||||
* Once set by MSIx message, an EVENTI is signal to the CPUs
|
||||
* cluster specified by ‘Local GIC Target List’
|
||||
* [18:17] - Target GIC ID
|
||||
* Specifies which IO-GIC (external shared GIC) is targeted
|
||||
* 0: Local GIC, as specified by the Local GIC Target List
|
||||
* 1: IO-GIC 0
|
||||
* 2: Reserved
|
||||
* 3: Reserved
|
||||
* [16:13] - Local GIC Target List
|
||||
* Specifies the Local GICs list targeted by this MSIx
|
||||
* message.
|
||||
* [16] If set, SPIn is set in Cluster 0 local GIC
|
||||
* [15:13] Reserved
|
||||
* [15] If set, SPIn is set in Cluster 1 local GIC
|
||||
* [14] If set, SPIn is set in Cluster 2 local GIC
|
||||
* [13] If set, SPIn is set in Cluster 3 local GIC
|
||||
* [12:3] - SPIn
|
||||
* Specifies the SPI (Shared Peripheral Interrupt) index to
|
||||
* be set in target GICs
|
||||
* Notes:
|
||||
* If targeting any local GIC than only SPI[249:0] are valid
|
||||
* [2] - Function vector
|
||||
* MSI Data vector extension hint
|
||||
* [1:0] - Reserved
|
||||
* Must be set to zero
|
||||
*/
|
||||
*addr = (uint64_t)sc->base_addr + (uint64_t)((1 << 16) + (spi << 3));
|
||||
*data = 0;
|
||||
|
||||
if (bootverbose)
|
||||
device_printf(dev, "MSI mapping: SPI: %d addr: %jx data: %x\n",
|
||||
spi, (uintmax_t)*addr, *data);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
al_msix_alloc_msi(device_t dev, device_t child, int count, int maxcount,
|
||||
device_t *pic, struct intr_irqsrc **srcs)
|
||||
{
|
||||
struct intr_map_data_fdt *fdt_data;
|
||||
struct al_msix_softc *sc;
|
||||
vmem_addr_t irq_base;
|
||||
int error;
|
||||
u_int i, j;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
if ((powerof2(count) == 0) || (count > 8))
|
||||
return (EINVAL);
|
||||
|
||||
if (vmem_alloc(sc->irq_alloc, count, M_FIRSTFIT | M_NOWAIT,
|
||||
&irq_base) != 0)
|
||||
return (ENOMEM);
|
||||
|
||||
/* Fabricate OFW data to get ISRC from GIC and return it */
|
||||
fdt_data = malloc(sizeof(*fdt_data) +
|
||||
GIC_INTR_CELL_CNT * sizeof(pcell_t), M_AL_MSIX, M_WAITOK);
|
||||
fdt_data->hdr.type = INTR_MAP_DATA_FDT;
|
||||
fdt_data->iparent = 0;
|
||||
fdt_data->ncells = GIC_INTR_CELL_CNT;
|
||||
fdt_data->cells[0] = AL_SPI_INTR; /* code for SPI interrupt */
|
||||
fdt_data->cells[1] = 0; /* SPI number (uninitialized) */
|
||||
fdt_data->cells[2] = AL_EDGE_HIGH; /* trig = edge, pol = high */
|
||||
|
||||
mtx_lock(&sc->msi_mtx);
|
||||
|
||||
for (i = irq_base; i < irq_base + count; i++) {
|
||||
fdt_data->cells[1] = sc->irq_min + i;
|
||||
error = PIC_MAP_INTR(sc->gic_dev,
|
||||
(struct intr_map_data *)fdt_data, srcs);
|
||||
if (error) {
|
||||
for (j = irq_base; j < i; j++)
|
||||
sc->isrcs[j] = NULL;
|
||||
mtx_unlock(&sc->msi_mtx);
|
||||
vmem_free(sc->irq_alloc, irq_base, count);
|
||||
free(fdt_data, M_AL_MSIX);
|
||||
return (error);
|
||||
}
|
||||
|
||||
sc->isrcs[i] = *srcs;
|
||||
srcs++;
|
||||
}
|
||||
|
||||
mtx_unlock(&sc->msi_mtx);
|
||||
free(fdt_data, M_AL_MSIX);
|
||||
|
||||
if (bootverbose)
|
||||
device_printf(dev,
|
||||
"MSI-X allocation: start SPI %d, count %d\n",
|
||||
(int)irq_base + sc->irq_min, count);
|
||||
|
||||
*pic = sc->gic_dev;
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
al_msix_release_msi(device_t dev, device_t child, int count,
|
||||
struct intr_irqsrc **srcs)
|
||||
{
|
||||
struct al_msix_softc *sc;
|
||||
int i, pos;
|
||||
|
||||
sc = device_get_softc(dev);
|
||||
|
||||
mtx_lock(&sc->msi_mtx);
|
||||
|
||||
pos = al_find_intr_pos_in_map(dev, *srcs);
|
||||
vmem_free(sc->irq_alloc, pos, count);
|
||||
for (i = 0; i < count; i++) {
|
||||
pos = al_find_intr_pos_in_map(dev, *srcs);
|
||||
if (pos != ERR_NOT_IN_MAP)
|
||||
sc->isrcs[pos] = NULL;
|
||||
srcs++;
|
||||
}
|
||||
|
||||
mtx_unlock(&sc->msi_mtx);
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
al_msix_alloc_msix(device_t dev, device_t child, device_t *pic,
|
||||
struct intr_irqsrc **isrcp)
|
||||
{
|
||||
|
||||
return (al_msix_alloc_msi(dev, child, 1, 1, pic, isrcp));
|
||||
}
|
||||
|
||||
static int
|
||||
al_msix_release_msix(device_t dev, device_t child, struct intr_irqsrc *isrc)
|
||||
{
|
||||
|
||||
return (al_msix_release_msi(dev, child, 1, &isrc));
|
||||
}
|
@ -170,6 +170,16 @@
|
||||
};
|
||||
};
|
||||
|
||||
/* MSIX Configuration */
|
||||
msix: msix {
|
||||
compatible = "annapurna-labs,al-msix";
|
||||
#address-cells = <2>;
|
||||
#size-cells = <1>;
|
||||
reg = <0xfbe00000 0x100000>;
|
||||
interrupts = <0 96 1 0 159 1>;
|
||||
interrupt-parent = <&MPIC>;
|
||||
};
|
||||
|
||||
pcie-internal {
|
||||
compatible = "annapurna-labs,al-internal-pcie";
|
||||
device_type = "pci";
|
||||
@ -182,6 +192,7 @@
|
||||
<0x3800 0 0 1 &MPIC 0 36 4>,
|
||||
<0x4000 0 0 1 &MPIC 0 43 4>, // SATA 0 (PCIe expander)
|
||||
<0x4800 0 0 1 &MPIC 0 44 1>; // SATA 1 (onboard)
|
||||
msi-parent = <&msix>;
|
||||
|
||||
// ranges:
|
||||
// - ECAM - non prefetchable config space
|
||||
|
@ -14,6 +14,7 @@ cloudabi32_vdso_blob.o optional compat_cloudabi32 \
|
||||
arm/annapurna/alpine/alpine_ccu.c optional al_ccu fdt
|
||||
arm/annapurna/alpine/alpine_nb_service.c optional al_nb_service fdt
|
||||
arm/annapurna/alpine/alpine_pci.c optional al_pci fdt
|
||||
arm/annapurna/alpine/alpine_pci_msix.c optional al_pci fdt
|
||||
arm/arm/autoconf.c standard
|
||||
arm/arm/bcopy_page.S standard
|
||||
arm/arm/bcopyinout.S standard
|
||||
|
@ -41,6 +41,7 @@ arm/allwinner/if_awg.c optional awg
|
||||
arm/annapurna/alpine/alpine_ccu.c optional al_ccu fdt
|
||||
arm/annapurna/alpine/alpine_nb_service.c optional al_nb_service fdt
|
||||
arm/annapurna/alpine/alpine_pci.c optional al_pci fdt
|
||||
arm/annapurna/alpine/alpine_pci_msix.c optional al_pci fdt
|
||||
arm/arm/generic_timer.c standard
|
||||
arm/arm/gic.c standard
|
||||
arm/arm/gic_fdt.c optional fdt
|
||||
|
Loading…
Reference in New Issue
Block a user