mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-27 11:55:06 +00:00
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:
parent
6d8433cb00
commit
4b771f1b99
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=292064
@ -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);
|
||||
|
@ -51,4 +51,6 @@ struct arm_gic_softc {
|
||||
uint32_t nirqs;
|
||||
};
|
||||
|
||||
int arm_gic_attach(device_t);
|
||||
|
||||
#endif
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user