Add support for the GICv2M extension to the GICv2 interrupt controller.

This is (oddly) specified in the ARM Server Base System Architecture. It
extends the GICv2 to support MSI and MSI-X interrupts, however only the
latter are currently supported.

Only the FDT attachment is currently supported, however the attachment
and core driver are split to help adding ACPI support in the future.

Obtained from:	ABT Systems Ltd
Relnotes:	yes
Sponsored by:	SoftIron Inc
This commit is contained in:
Andrew Turner 2015-12-10 16:40:38 +00:00
parent 6d8433cb00
commit 4b771f1b99
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=292064
3 changed files with 325 additions and 2 deletions

View File

@ -47,10 +47,14 @@ __FBSDID("$FreeBSD$");
#include <sys/cpuset.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <machine/smp.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <arm64/arm64/gic.h>
#include "pic_if.h"
@ -153,7 +157,7 @@ gic_init_secondary(device_t dev)
}
#endif
static int
int
arm_gic_attach(device_t dev)
{
struct arm_gic_softc *sc;
@ -344,3 +348,116 @@ static device_method_t arm_gic_methods[] = {
DEFINE_CLASS_0(gic, arm_gic_driver, arm_gic_methods,
sizeof(struct arm_gic_softc));
#define GICV2M_MSI_TYPER 0x008
#define MSI_TYPER_SPI_BASE(x) (((x) >> 16) & 0x3ff)
#define MSI_TYPER_SPI_COUNT(x) (((x) >> 0) & 0x3ff)
#define GICv2M_MSI_SETSPI_NS 0x040
#define GICV2M_MSI_IIDR 0xFCC
struct gicv2m_softc {
struct resource *sc_mem;
struct mtx sc_mutex;
u_int sc_spi_start;
u_int sc_spi_count;
u_int sc_spi_offset;
};
static int
gicv2m_probe(device_t dev)
{
device_set_desc(dev, "ARM Generic Interrupt Controller MSI/MSIX");
return (BUS_PROBE_DEFAULT);
}
static int
gicv2m_attach(device_t dev)
{
struct gicv2m_softc *sc;
uint32_t typer;
int rid;
sc = device_get_softc(dev);
rid = 0;
sc->sc_mem = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE);
if (sc->sc_mem == NULL) {
device_printf(dev, "Unable to allocate resources\n");
return (ENXIO);
}
typer = bus_read_4(sc->sc_mem, GICV2M_MSI_TYPER);
sc->sc_spi_start = MSI_TYPER_SPI_BASE(typer);
sc->sc_spi_count = MSI_TYPER_SPI_COUNT(typer);
device_printf(dev, "using spi %u to %u\n", sc->sc_spi_start,
sc->sc_spi_start + sc->sc_spi_count - 1);
mtx_init(&sc->sc_mutex, "GICv2m lock", "", MTX_DEF);
arm_register_msi_pic(dev);
return (0);
}
static int
gicv2m_alloc_msix(device_t dev, device_t pci_dev, int *pirq)
{
struct arm_gic_softc *psc;
struct gicv2m_softc *sc;
uint32_t reg;
int irq;
psc = device_get_softc(device_get_parent(dev));
sc = device_get_softc(dev);
mtx_lock(&sc->sc_mutex);
/* Find an unused interrupt */
KASSERT(sc->sc_spi_offset < sc->sc_spi_count, ("No free SPIs"));
irq = sc->sc_spi_start + sc->sc_spi_offset;
sc->sc_spi_offset++;
/* Interrupts need to be edge triggered, set this */
reg = gic_d_read_4(psc, GICD_ICFGR(irq >> 4));
reg |= (GICD_ICFGR_TRIG_EDGE | GICD_ICFGR_POL_HIGH) <<
((irq & 0xf) * 2);
gic_d_write_4(psc, GICD_ICFGR(irq >> 4), reg);
*pirq = irq;
mtx_unlock(&sc->sc_mutex);
return (0);
}
static int
gicv2m_map_msi(device_t dev, device_t pci_dev, int irq, uint64_t *addr,
uint32_t *data)
{
struct gicv2m_softc *sc = device_get_softc(dev);
*addr = vtophys(rman_get_virtual(sc->sc_mem)) + 0x40;
*data = irq;
return (0);
}
static device_method_t arm_gicv2m_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, gicv2m_probe),
DEVMETHOD(device_attach, gicv2m_attach),
/* MSI-X */
DEVMETHOD(pic_alloc_msix, gicv2m_alloc_msix),
DEVMETHOD(pic_map_msi, gicv2m_map_msi),
{ 0, 0 }
};
static devclass_t arm_gicv2m_devclass;
DEFINE_CLASS_0(gicv2m, arm_gicv2m_driver, arm_gicv2m_methods,
sizeof(struct gicv2m_softc));
EARLY_DRIVER_MODULE(gicv2m, gic, arm_gicv2m_driver, arm_gicv2m_devclass,
0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);

View File

@ -51,4 +51,6 @@ struct arm_gic_softc {
uint32_t nirqs;
};
int arm_gic_attach(device_t);
#endif

View File

@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$");
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <machine/bus.h>
@ -56,6 +57,68 @@ static struct ofw_compat_data compat_data[] = {
{NULL, false}
};
struct gic_range {
uint64_t bus;
uint64_t host;
uint64_t size;
};
struct arm_gic_fdt_softc {
struct arm_gic_softc sc_gic;
pcell_t sc_host_cells;
pcell_t sc_addr_cells;
pcell_t sc_size_cells;
struct gic_range *sc_ranges;
int sc_nranges;
};
struct gic_devinfo {
struct ofw_bus_devinfo obdinfo;
struct resource_list rl;
};
static int
gic_fill_ranges(phandle_t node, struct arm_gic_fdt_softc *sc)
{
cell_t *base_ranges;
ssize_t nbase_ranges;
int i, j, k;
nbase_ranges = OF_getproplen(node, "ranges");
if (nbase_ranges < 0)
return (-1);
sc->sc_nranges = nbase_ranges / sizeof(cell_t) /
(sc->sc_addr_cells + sc->sc_host_cells + sc->sc_size_cells);
if (sc->sc_nranges == 0)
return (0);
sc->sc_ranges = malloc(sc->sc_nranges * sizeof(sc->sc_ranges[0]),
M_DEVBUF, M_WAITOK);
base_ranges = malloc(nbase_ranges, M_DEVBUF, M_WAITOK);
OF_getencprop(node, "ranges", base_ranges, nbase_ranges);
for (i = 0, j = 0; i < sc->sc_nranges; i++) {
sc->sc_ranges[i].bus = 0;
for (k = 0; k < sc->sc_addr_cells; k++) {
sc->sc_ranges[i].bus <<= 32;
sc->sc_ranges[i].bus |= base_ranges[j++];
}
sc->sc_ranges[i].host = 0;
for (k = 0; k < sc->sc_host_cells; k++) {
sc->sc_ranges[i].host <<= 32;
sc->sc_ranges[i].host |= base_ranges[j++];
}
sc->sc_ranges[i].size = 0;
for (k = 0; k < sc->sc_size_cells; k++) {
sc->sc_ranges[i].size <<= 32;
sc->sc_ranges[i].size |= base_ranges[j++];
}
}
free(base_ranges, M_DEVBUF);
return (sc->sc_nranges);
}
static int
arm_gic_fdt_probe(device_t dev)
{
@ -70,15 +133,156 @@ arm_gic_fdt_probe(device_t dev)
return (BUS_PROBE_DEFAULT);
}
static int
arm_gic_fdt_attach(device_t dev)
{
struct arm_gic_fdt_softc *sc = device_get_softc(dev);
phandle_t root, child;
struct gic_devinfo *dinfo;
device_t cdev;
int err;
err = arm_gic_attach(dev);
if (err != 0)
return (err);
root = ofw_bus_get_node(dev);
sc->sc_host_cells = 1;
OF_getencprop(OF_parent(root), "#address-cells", &sc->sc_host_cells,
sizeof(sc->sc_host_cells));
sc->sc_addr_cells = 2;
OF_getencprop(root, "#address-cells", &sc->sc_addr_cells,
sizeof(sc->sc_addr_cells));
sc->sc_size_cells = 2;
OF_getencprop(root, "#size-cells", &sc->sc_size_cells,
sizeof(sc->sc_size_cells));
if (gic_fill_ranges(root, sc) < 0) {
device_printf(dev, "could not get ranges\n");
return (ENXIO);
}
for (child = OF_child(root); child != 0; child = OF_peer(child)) {
dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_WAITOK | M_ZERO);
if (ofw_bus_gen_setup_devinfo(&dinfo->obdinfo, child) != 0) {
free(dinfo, M_DEVBUF);
continue;
}
resource_list_init(&dinfo->rl);
ofw_bus_reg_to_rl(dev, child, sc->sc_addr_cells,
sc->sc_size_cells, &dinfo->rl);
cdev = device_add_child(dev, NULL, -1);
if (cdev == NULL) {
device_printf(dev, "<%s>: device_add_child failed\n",
dinfo->obdinfo.obd_name);
resource_list_free(&dinfo->rl);
ofw_bus_gen_destroy_devinfo(&dinfo->obdinfo);
free(dinfo, M_DEVBUF);
continue;
}
device_set_ivars(cdev, dinfo);
}
bus_generic_probe(dev);
return (bus_generic_attach(dev));
}
static struct resource *
arm_gic_fdt_alloc_resource(device_t bus, device_t child, int type, int *rid,
u_long start, u_long end, u_long count, u_int flags)
{
struct arm_gic_fdt_softc *sc = device_get_softc(bus);
struct gic_devinfo *di;
struct resource_list_entry *rle;
int j;
KASSERT(type == SYS_RES_MEMORY, ("Invalid resoure type %x", type));
/*
* Request for the default allocation with a given rid: use resource
* list stored in the local device info.
*/
if ((start == 0UL) && (end == ~0UL)) {
if ((di = device_get_ivars(child)) == NULL)
return (NULL);
if (type == SYS_RES_IOPORT)
type = SYS_RES_MEMORY;
rle = resource_list_find(&di->rl, type, *rid);
if (rle == NULL) {
if (bootverbose)
device_printf(bus, "no default resources for "
"rid = %d, type = %d\n", *rid, type);
return (NULL);
}
start = rle->start;
end = rle->end;
count = rle->count;
}
/* Remap through ranges property */
for (j = 0; j < sc->sc_nranges; j++) {
if (start >= sc->sc_ranges[j].bus && end <
sc->sc_ranges[j].bus + sc->sc_ranges[j].size) {
start -= sc->sc_ranges[j].bus;
start += sc->sc_ranges[j].host;
end -= sc->sc_ranges[j].bus;
end += sc->sc_ranges[j].host;
break;
}
}
if (j == sc->sc_nranges && sc->sc_nranges != 0) {
if (bootverbose)
device_printf(bus, "Could not map resource "
"%#lx-%#lx\n", start, end);
return (NULL);
}
return (bus_generic_alloc_resource(bus, child, type, rid, start, end,
count, flags));
}
static const struct ofw_bus_devinfo *
arm_gic_fdt_ofw_get_devinfo(device_t bus __unused, device_t child)
{
struct gic_devinfo *di;
di = device_get_ivars(child);
return (&di->obdinfo);
}
static device_method_t arm_gic_fdt_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, arm_gic_fdt_probe),
DEVMETHOD(device_attach, arm_gic_fdt_attach),
/* Bus interface */
DEVMETHOD(bus_add_child, bus_generic_add_child),
DEVMETHOD(bus_alloc_resource, arm_gic_fdt_alloc_resource),
DEVMETHOD(bus_release_resource, bus_generic_release_resource),
DEVMETHOD(bus_activate_resource,bus_generic_activate_resource),
/* ofw_bus interface */
DEVMETHOD(ofw_bus_get_devinfo, arm_gic_fdt_ofw_get_devinfo),
DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat),
DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model),
DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name),
DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node),
DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type),
DEVMETHOD_END
};
DEFINE_CLASS_1(gic, arm_gic_fdt_driver, arm_gic_fdt_methods,
sizeof(struct arm_gic_softc), arm_gic_driver);
sizeof(struct arm_gic_fdt_softc), arm_gic_driver);
static devclass_t arm_gic_fdt_devclass;