svn commit: r292064 - head/sys/arm64/arm64

Andrew Turner andrew at FreeBSD.org
Thu Dec 10 16:40:40 UTC 2015


Author: andrew
Date: Thu Dec 10 16:40:38 2015
New Revision: 292064
URL: https://svnweb.freebsd.org/changeset/base/292064

Log:
  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

Modified:
  head/sys/arm64/arm64/gic.c
  head/sys/arm64/arm64/gic.h
  head/sys/arm64/arm64/gic_fdt.c

Modified: head/sys/arm64/arm64/gic.c
==============================================================================
--- head/sys/arm64/arm64/gic.c	Thu Dec 10 15:51:02 2015	(r292063)
+++ head/sys/arm64/arm64/gic.c	Thu Dec 10 16:40:38 2015	(r292064)
@@ -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);

Modified: head/sys/arm64/arm64/gic.h
==============================================================================
--- head/sys/arm64/arm64/gic.h	Thu Dec 10 15:51:02 2015	(r292063)
+++ head/sys/arm64/arm64/gic.h	Thu Dec 10 16:40:38 2015	(r292064)
@@ -51,4 +51,6 @@ struct arm_gic_softc {
 	uint32_t		nirqs;
 };
 
+int arm_gic_attach(device_t);
+
 #endif

Modified: head/sys/arm64/arm64/gic_fdt.c
==============================================================================
--- head/sys/arm64/arm64/gic_fdt.c	Thu Dec 10 15:51:02 2015	(r292063)
+++ head/sys/arm64/arm64/gic_fdt.c	Thu Dec 10 16:40:38 2015	(r292064)
@@ -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_dat
 	{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;
 


More information about the svn-src-head mailing list