From 4b771f1b997bc3b9ea5a83359d54d64d2d7c6dba Mon Sep 17 00:00:00 2001 From: Andrew Turner Date: Thu, 10 Dec 2015 16:40:38 +0000 Subject: [PATCH] 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 --- sys/arm64/arm64/gic.c | 119 +++++++++++++++++++++- sys/arm64/arm64/gic.h | 2 + sys/arm64/arm64/gic_fdt.c | 206 +++++++++++++++++++++++++++++++++++++- 3 files changed, 325 insertions(+), 2 deletions(-) diff --git a/sys/arm64/arm64/gic.c b/sys/arm64/arm64/gic.c index 7c0692ea790a..7ac88f32b224 100644 --- a/sys/arm64/arm64/gic.c +++ b/sys/arm64/arm64/gic.c @@ -47,10 +47,14 @@ __FBSDID("$FreeBSD$"); #include #include #include + #include #include #include +#include +#include + #include #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); diff --git a/sys/arm64/arm64/gic.h b/sys/arm64/arm64/gic.h index 2660884291ea..a8f1c2520462 100644 --- a/sys/arm64/arm64/gic.h +++ b/sys/arm64/arm64/gic.h @@ -51,4 +51,6 @@ struct arm_gic_softc { uint32_t nirqs; }; +int arm_gic_attach(device_t); + #endif diff --git a/sys/arm64/arm64/gic_fdt.c b/sys/arm64/arm64/gic_fdt.c index 6c9338ad85b7..a4c4e2536f7d 100644 --- a/sys/arm64/arm64/gic_fdt.c +++ b/sys/arm64/arm64/gic_fdt.c @@ -34,6 +34,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include @@ -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;