powerpc/mpc85xx: Add MSI support for Freescale PowerPC SoCs

Freescale SoCs use a set of IRQs at the high end of the OpenPIC IRQ
list, not counted in the NIRQs of the Feature reporting register.  Some
SoCs include a MSI inbound window in the PCIe controller configuration
registers as well, but some don't.  Currently, this only handles the
SoCs *with* the MSI window.

There are 256 MSIs per MSI bank (32 per MSI IRQ, 8 IRQs per MSI bank).
The P5020 has 3 banks, yielding up to 768 MSIs; older SoCs have only one
bank.
This commit is contained in:
jhibbits 2019-11-08 03:36:19 +00:00
parent 8dc188276e
commit bdcbfe133b
4 changed files with 274 additions and 13 deletions

View File

@ -35,6 +35,7 @@
#define OPENPIC_IRQMAX 256 /* h/w allows more */
#define OPENPIC_QUIRK_SINGLE_BIND 1 /* Bind interrupts to only 1 CPU */
#define OPENPIC_QUIRK_HIDDEN_IRQS 2 /* May have IRQs beyond FRR[NIRQ] */
/* Names match the macros in openpicreg.h. */
struct openpic_timer {

View File

@ -51,8 +51,10 @@ __FBSDID("$FreeBSD$");
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/rman.h>
#include <sys/endian.h>
#include <sys/vmem.h>
#include <vm/vm.h>
#include <vm/pmap.h>
@ -67,6 +69,7 @@ __FBSDID("$FreeBSD$");
#include "ofw_bus_if.h"
#include "pcib_if.h"
#include "pic_if.h"
#include <machine/resource.h>
#include <machine/bus.h>
@ -80,6 +83,12 @@ __FBSDID("$FreeBSD$");
#define REG_CFG_DATA 0x0004
#define REG_INT_ACK 0x0008
#define REG_PEX_IP_BLK_REV1 0x0bf8
#define IP_MJ_M 0x0000ff00
#define IP_MJ_S 8
#define IP_MN_M 0x000000ff
#define IP_MN_S 0
#define REG_POTAR(n) (0x0c00 + 0x20 * (n))
#define REG_POTEAR(n) (0x0c04 + 0x20 * (n))
#define REG_POWBAR(n) (0x0c08 + 0x20 * (n))
@ -89,6 +98,12 @@ __FBSDID("$FreeBSD$");
#define REG_PIWBAR(n) (0x0e08 - 0x20 * (n))
#define REG_PIWBEAR(n) (0x0e0c - 0x20 * (n))
#define REG_PIWAR(n) (0x0e10 - 0x20 * (n))
#define PIWAR_EN 0x80000000
#define PIWAR_PF 0x40000000
#define PIWAR_TRGT_M 0x00f00000
#define PIWAR_TRGT_S 20
#define PIWAR_TRGT_CCSR 0xe
#define PIWAR_TRGT_LOCAL 0xf
#define REG_PEX_MES_DR 0x0020
#define REG_PEX_MES_IER 0x0028
@ -123,10 +138,14 @@ __FBSDID("$FreeBSD$");
#define DEVFN(b, s, f) ((b << 16) | (s << 8) | f)
#define FSL_NUM_MSIS 256 /* 8 registers of 32 bits (8 hardware IRQs) */
struct fsl_pcib_softc {
struct ofw_pci_softc pci_sc;
device_t sc_dev;
struct mtx sc_cfg_mtx;
int sc_ip_maj;
int sc_ip_min;
int sc_iomem_target;
bus_addr_t sc_iomem_start, sc_iomem_end;
@ -151,6 +170,14 @@ struct fsl_pcib_err_dr {
uint32_t err_dr_mask;
};
struct fsl_msi_map {
SLIST_ENTRY(fsl_msi_map) slist;
uint32_t irq_base;
bus_addr_t target;
};
SLIST_HEAD(msi_head, fsl_msi_map) fsl_msis = SLIST_HEAD_INITIALIZER(msi_head);
static const struct fsl_pcib_err_dr pci_err[] = {
{"ME", REG_PEX_ERR_DR_ME},
{"PCT", REG_PEX_ERR_DR_PCT},
@ -195,6 +222,16 @@ static int fsl_pcib_maxslots(device_t);
static uint32_t fsl_pcib_read_config(device_t, u_int, u_int, u_int, u_int, int);
static void fsl_pcib_write_config(device_t, u_int, u_int, u_int, u_int,
uint32_t, int);
static int fsl_pcib_alloc_msi(device_t dev, device_t child,
int count, int maxcount, int *irqs);
static int fsl_pcib_release_msi(device_t dev, device_t child,
int count, int *irqs);
static int fsl_pcib_alloc_msix(device_t dev, device_t child, int *irq);
static int fsl_pcib_release_msix(device_t dev, device_t child, int irq);
static int fsl_pcib_map_msi(device_t dev, device_t child,
int irq, uint64_t *addr, uint32_t *data);
static vmem_t *msi_vmem; /* Global MSI vmem, holds all MSI ranges. */
/*
* Bus interface definitions.
@ -209,6 +246,11 @@ static device_method_t fsl_pcib_methods[] = {
DEVMETHOD(pcib_maxslots, fsl_pcib_maxslots),
DEVMETHOD(pcib_read_config, fsl_pcib_read_config),
DEVMETHOD(pcib_write_config, fsl_pcib_write_config),
DEVMETHOD(pcib_alloc_msi, fsl_pcib_alloc_msi),
DEVMETHOD(pcib_release_msi, fsl_pcib_release_msi),
DEVMETHOD(pcib_alloc_msix, fsl_pcib_alloc_msix),
DEVMETHOD(pcib_release_msix, fsl_pcib_release_msix),
DEVMETHOD(pcib_map_msi, fsl_pcib_map_msi),
DEVMETHOD_END
};
@ -272,7 +314,7 @@ fsl_pcib_attach(device_t dev)
{
struct fsl_pcib_softc *sc;
phandle_t node;
uint32_t cfgreg, brctl;
uint32_t cfgreg, brctl, ipreg;
int error, rid;
uint8_t ltssm, capptr;
@ -290,6 +332,9 @@ fsl_pcib_attach(device_t dev)
sc->sc_bsh = rman_get_bushandle(sc->sc_res);
sc->sc_busnr = 0;
ipreg = bus_read_4(sc->sc_res, REG_PEX_IP_BLK_REV1);
sc->sc_ip_min = (ipreg & IP_MN_M) >> IP_MN_S;
sc->sc_ip_maj = (ipreg & IP_MJ_M) >> IP_MJ_S;
mtx_init(&sc->sc_cfg_mtx, "pcicfg", NULL, MTX_SPIN);
cfgreg = fsl_pcib_cfgread(sc, 0, 0, 0, PCIR_VENDOR, 2);
@ -533,14 +578,16 @@ fsl_pcib_inbound(struct fsl_pcib_softc *sc, int wnd, int tgt, uint64_t start,
KASSERT(wnd > 0, ("%s: inbound window 0 is invalid", __func__));
attr = PIWAR_EN;
switch (tgt) {
/* XXX OCP85XX_TGTIF_RAM2, OCP85XX_TGTIF_RAM_INTL should be handled */
case OCP85XX_TGTIF_RAM1_85XX:
case OCP85XX_TGTIF_RAM1_QORIQ:
attr = 0xa0f55000 | (ffsl(size) - 2);
case -1:
attr &= ~PIWAR_EN;
break;
case PIWAR_TRGT_LOCAL:
attr |= (ffsl(size) - 2);
default:
attr = 0;
attr |= (tgt << PIWAR_TRGT_S);
break;
}
tar = start >> 12;
@ -702,8 +749,209 @@ fsl_pcib_decode_win(phandle_t node, struct fsl_pcib_softc *sc)
fsl_pcib_inbound(sc, 1, -1, 0, 0, 0);
fsl_pcib_inbound(sc, 2, -1, 0, 0, 0);
fsl_pcib_inbound(sc, 3, OCP85XX_TGTIF_RAM1, 0,
2U * 1024U * 1024U * 1024U, 0);
fsl_pcib_inbound(sc, 3, PIWAR_TRGT_LOCAL, 0,
ptoa(Maxmem), 0);
/* Direct-map the CCSR for MSIs. */
/* Freescale PCIe 2.x has a dedicated MSI window. */
/* inbound window 8 makes it hit 0xD00 offset, the MSI window. */
if (sc->sc_ip_maj >= 2)
fsl_pcib_inbound(sc, 8, PIWAR_TRGT_CCSR, ccsrbar_pa,
ccsrbar_size, ccsrbar_pa);
else
fsl_pcib_inbound(sc, 1, PIWAR_TRGT_CCSR, ccsrbar_pa,
ccsrbar_size, ccsrbar_pa);
return (0);
}
static int fsl_pcib_alloc_msi(device_t dev, device_t child,
int count, int maxcount, int *irqs)
{
struct fsl_pcib_softc *sc;
vmem_addr_t start;
int err, i;
sc = device_get_softc(dev);
if (msi_vmem == NULL)
return (ENODEV);
err = vmem_xalloc(msi_vmem, count, powerof2(count), 0, 0,
VMEM_ADDR_MIN, VMEM_ADDR_MAX, M_BESTFIT | M_WAITOK, &start);
if (err)
return (err);
for (i = 0; i < count; i++)
irqs[i] = start + i;
return (0);
}
static int fsl_pcib_release_msi(device_t dev, device_t child,
int count, int *irqs)
{
if (msi_vmem == NULL)
return (ENODEV);
vmem_xfree(msi_vmem, irqs[0], count);
return (0);
}
static int fsl_pcib_alloc_msix(device_t dev, device_t child, int *irq)
{
return (fsl_pcib_alloc_msi(dev, child, 1, 1, irq));
}
static int fsl_pcib_release_msix(device_t dev, device_t child, int irq)
{
return (fsl_pcib_release_msi(dev, child, 1, &irq));
}
static int fsl_pcib_map_msi(device_t dev, device_t child,
int irq, uint64_t *addr, uint32_t *data)
{
struct fsl_msi_map *mp;
SLIST_FOREACH(mp, &fsl_msis, slist) {
if (irq >= mp->irq_base && irq < mp->irq_base + FSL_NUM_MSIS)
break;
}
if (mp == NULL)
return (ENODEV);
*data = (irq & 255);
*addr = ccsrbar_pa + mp->target;
return (0);
}
/*
* Linux device trees put the msi@<x> as children of the SoC, with ranges based
* on the CCSR. Since rman doesn't permit overlapping or sub-ranges between
* devices (bus_space_subregion(9) could do it, but let's not touch the PIC
* driver just to allocate a subregion for a sibling driver). This driver will
* use ccsr_write() and ccsr_read() instead.
*/
#define FSL_NUM_IRQS 8
#define FSL_NUM_MSI_PER_IRQ 32
#define FSL_MSI_TARGET 0x140
struct fsl_msi_softc {
vm_offset_t sc_base;
vm_offset_t sc_target;
int sc_msi_base_irq;
struct fsl_msi_map sc_map;
struct fsl_msi_irq {
/* This struct gets passed as the filter private data. */
struct fsl_msi_softc *sc_ptr; /* Pointer back to softc. */
struct resource *res;
int irq;
void *cookie;
int vectors[FSL_NUM_MSI_PER_IRQ];
vm_offset_t reg;
} sc_msi_irq[FSL_NUM_IRQS];
};
static int
fsl_msi_intr_filter(void *priv)
{
struct fsl_msi_irq *data = priv;
uint32_t reg;
int i;
reg = ccsr_read4(ccsrbar_va + data->reg);
i = 0;
while (reg != 0) {
if (reg & 1)
powerpc_dispatch_intr(data->vectors[i], NULL);
reg >>= 1;
i++;
}
return (FILTER_HANDLED);
}
static int
fsl_msi_probe(device_t dev)
{
if (!ofw_bus_is_compatible(dev, "fsl,mpic-msi"))
return (ENXIO);
device_set_desc(dev, "Freescale MSI");
return (BUS_PROBE_DEFAULT);
}
static int
fsl_msi_attach(device_t dev)
{
struct fsl_msi_softc *sc;
struct fsl_msi_irq *irq;
int i;
sc = device_get_softc(dev);
if (msi_vmem == NULL)
msi_vmem = vmem_create("MPIC MSI", 0, 0, 1, 1, M_BESTFIT | M_WAITOK);
/* Manually play with resource entries. */
sc->sc_base = bus_get_resource_start(dev, SYS_RES_MEMORY, 0);
sc->sc_map.target = bus_get_resource_start(dev, SYS_RES_MEMORY, 1);
if (sc->sc_map.target == 0)
sc->sc_map.target = sc->sc_base + FSL_MSI_TARGET;
for (i = 0; i < FSL_NUM_IRQS; i++) {
irq = &sc->sc_msi_irq[i];
irq->irq = i;
irq->reg = sc->sc_base + 16 * i;
irq->res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&irq->irq, RF_ACTIVE);
bus_setup_intr(dev, irq->res, INTR_TYPE_MISC | INTR_MPSAFE,
fsl_msi_intr_filter, NULL, irq, &irq->cookie);
}
sc->sc_map.irq_base = powerpc_register_pic(dev, ofw_bus_get_node(dev),
FSL_NUM_MSIS, 0, 0);
/* Let vmem and the IRQ subsystem work their magic for allocations. */
vmem_add(msi_vmem, sc->sc_map.irq_base, FSL_NUM_MSIS, M_WAITOK);
SLIST_INSERT_HEAD(&fsl_msis, &sc->sc_map, slist);
return (0);
}
static void
fsl_msi_enable(device_t dev, u_int irq, u_int vector, void **priv)
{
struct fsl_msi_softc *sc;
struct fsl_msi_irq *irqd;
sc = device_get_softc(dev);
irqd = &sc->sc_msi_irq[irq / FSL_NUM_MSI_PER_IRQ];
irqd->vectors[irq % FSL_NUM_MSI_PER_IRQ] = vector;
}
static device_method_t fsl_msi_methods[] = {
DEVMETHOD(device_probe, fsl_msi_probe),
DEVMETHOD(device_attach, fsl_msi_attach),
DEVMETHOD(pic_enable, fsl_msi_enable),
DEVMETHOD_END
};
static devclass_t fsl_msi_devclass;
static driver_t fsl_msi_driver = {
"fsl_msi",
fsl_msi_methods,
sizeof(struct fsl_msi_softc)
};
EARLY_DRIVER_MODULE(fsl_msi, simplebus, fsl_msi_driver, fsl_msi_devclass, 0, 0,
BUS_PASS_INTERRUPT + 1);

View File

@ -139,8 +139,10 @@ openpic_ofw_attach(device_t dev)
OF_getencprop(node, "linux,phandle", &xref, sizeof(xref)) == -1)
xref = node;
if (ofw_bus_is_compatible(dev, "fsl,mpic"))
if (ofw_bus_is_compatible(dev, "fsl,mpic")) {
sc->sc_quirks = OPENPIC_QUIRK_SINGLE_BIND;
sc->sc_quirks |= OPENPIC_QUIRK_HIDDEN_IRQS;
}
return (openpic_common_attach(dev, xref));
}

View File

@ -52,6 +52,8 @@
#include "pic_if.h"
#define OPENPIC_NIPIS 4
devclass_t openpic_devclass;
/*
@ -182,6 +184,14 @@ openpic_common_attach(device_t dev, uint32_t node)
"Version %s, supports %d CPUs and %d irqs\n",
sc->sc_version, sc->sc_ncpu, sc->sc_nirq);
/*
* Allow more IRQs than what the PIC says it handles. Some Freescale PICs
* have MSIs that show up above the PIC's self-described 196 IRQs
* (P5020 starts MSI IRQs at 224).
*/
if (sc->sc_quirks & OPENPIC_QUIRK_HIDDEN_IRQS)
sc->sc_nirq = OPENPIC_IRQMAX - OPENPIC_NIPIS;
for (cpu = 0; cpu < sc->sc_ncpu; cpu++)
openpic_write(sc, OPENPIC_PCPU_TPR(cpu), 15);
@ -196,7 +206,7 @@ openpic_common_attach(device_t dev, uint32_t node)
}
/* Reset and disable all IPIs. */
for (ipi = 0; ipi < 4; ipi++) {
for (ipi = 0; ipi < OPENPIC_NIPIS; ipi++) {
x = sc->sc_nirq + ipi;
x |= OPENPIC_IMASK;
x |= 15 << OPENPIC_PRIORITY_SHIFT;
@ -221,7 +231,7 @@ openpic_common_attach(device_t dev, uint32_t node)
for (cpu = 0; cpu < sc->sc_ncpu; cpu++)
openpic_write(sc, OPENPIC_PCPU_TPR(cpu), 0);
powerpc_register_pic(dev, node, sc->sc_nirq, 4, FALSE);
powerpc_register_pic(dev, node, sc->sc_nirq, OPENPIC_NIPIS, FALSE);
/* If this is not a cascaded PIC, it must be the root PIC */
if (sc->sc_intr == NULL)
@ -411,7 +421,7 @@ openpic_suspend(device_t dev)
sc = device_get_softc(dev);
sc->sc_saved_config = bus_read_4(sc->sc_memr, OPENPIC_CONFIG);
for (i = 0; i < 4; i++) {
for (i = 0; i < OPENPIC_NIPIS; i++) {
sc->sc_saved_ipis[i] = bus_read_4(sc->sc_memr, OPENPIC_IPI_VECTOR(i));
}
@ -442,7 +452,7 @@ openpic_resume(device_t dev)
sc = device_get_softc(dev);
sc->sc_saved_config = bus_read_4(sc->sc_memr, OPENPIC_CONFIG);
for (i = 0; i < 4; i++) {
for (i = 0; i < OPENPIC_NIPIS; i++) {
bus_write_4(sc->sc_memr, OPENPIC_IPI_VECTOR(i), sc->sc_saved_ipis[i]);
}