git: 9556306213e1 - main - arm64: Add ACPI support to GICv5 driver

From: Andrew Turner <andrew_at_FreeBSD.org>
Date: Thu, 17 Sep 2026 09:56:05 UTC
The branch main has been updated by andrew:

URL: https://cgit.FreeBSD.org/src/commit/?id=9556306213e1230fc3bef2598129f91e344bd49d

commit 9556306213e1230fc3bef2598129f91e344bd49d
Author:     Sarah Walker <sarah.walker2@arm.com>
AuthorDate: 2026-08-04 12:38:52 +0000
Commit:     Andrew Turner <andrew@FreeBSD.org>
CommitDate: 2026-09-17 09:20:22 +0000

    arm64: Add ACPI support to GICv5 driver
    
    Add ACPI support to the GICv5 IRS, ITS and IWB drivers. Device parameters are
    derived from the MADT and IORT tables.
    
    Reviewed by:    andrew
    Sponsored by:   Arm Ltd
    Differential Revision:  https://reviews.freebsd.org/D59171
---
 sys/arm/arm/gic_acpi.c       |   3 +
 sys/arm64/arm64/gicv3_its.c  |   2 +-
 sys/arm64/arm64/gicv5.c      |  50 +++++
 sys/arm64/arm64/gicv5_acpi.c | 455 +++++++++++++++++++++++++++++++++++++++++++
 sys/arm64/arm64/gicv5_its.c  | 138 +++++++++++++
 sys/arm64/arm64/gicv5_iwb.c  |  93 +++++++++
 sys/arm64/arm64/gicv5var.h   |   2 +
 sys/conf/files.arm64         |   5 +-
 8 files changed, 745 insertions(+), 3 deletions(-)

diff --git a/sys/arm/arm/gic_acpi.c b/sys/arm/arm/gic_acpi.c
index 6c8257beddfb..b64bfa3fedfd 100644
--- a/sys/arm/arm/gic_acpi.c
+++ b/sys/arm/arm/gic_acpi.c
@@ -139,6 +139,9 @@ gic_acpi_identify(driver_t *driver, device_t parent)
 	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
 	    madt_handler, &madt_data);
 
+	if (!madt_data.dist)
+		return;
+
 	/* Check the version of the GIC we have */
 	switch (madt_data.dist->Version) {
 	case ACPI_MADT_GIC_VERSION_NONE:
diff --git a/sys/arm64/arm64/gicv3_its.c b/sys/arm64/arm64/gicv3_its.c
index 42f0fdb2697d..5d91e23830ab 100644
--- a/sys/arm64/arm64/gicv3_its.c
+++ b/sys/arm64/arm64/gicv3_its.c
@@ -2399,7 +2399,7 @@ gicv3_its_acpi_probe(device_t dev)
 	if (gic_get_bus(dev) != GIC_BUS_ACPI)
 		return (EINVAL);
 
-	if (gic_get_hw_rev(dev) < 3)
+	if (gic_get_hw_rev(dev) < 3 || gic_get_hw_rev(dev) > 4)
 		return (EINVAL);
 
 	if (!gic_get_support_lpis(dev))
diff --git a/sys/arm64/arm64/gicv5.c b/sys/arm64/arm64/gicv5.c
index a533baacf489..c2d2c31bdc00 100644
--- a/sys/arm64/arm64/gicv5.c
+++ b/sys/arm64/arm64/gicv5.c
@@ -55,6 +55,11 @@
 #include <dev/ofw/ofw_bus_subr.h>
 #endif
 
+#ifdef DEV_ACPI
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+#endif
+
 #include "pic_if.h"
 
 #include <arm/arm/gic_common.h>
@@ -1166,6 +1171,26 @@ gic_map_fdt(device_t dev, u_int ncells, pcell_t *cells, bool *ppi, u_int *irqp,
 }
 #endif
 
+#ifdef DEV_ACPI
+static int
+do_gicv5_map_iwb_intr(struct intr_map_data *data,
+    struct intr_irqsrc **isrcp)
+{
+	struct intr_map_data_acpi *daa;
+	device_t iwb;
+	int iwb_id;
+
+	daa = (struct intr_map_data_acpi *)data;
+	iwb_id = (daa->irq & GSI_IWB_ID_MASK) >> GSI_IWB_ID_SHIFT;
+	iwb = acpi_iort_get_iwb_dev(iwb_id);
+
+	if (iwb == NULL)
+		return (ENODEV);
+
+	return (PIC_MAP_INTR(iwb, data, isrcp));
+}
+#endif
+
 static int
 do_gicv5_map_intr(device_t dev, struct intr_map_data *data, bool *ppip,
     u_int *irqp, enum intr_polarity *polp, enum intr_trigger *trigp)
@@ -1175,6 +1200,9 @@ do_gicv5_map_intr(device_t dev, struct intr_map_data *data, bool *ppip,
 	enum intr_trigger trig;
 #ifdef FDT
 	struct intr_map_data_fdt *daf;
+#endif
+#ifdef DEV_ACPI
+	struct intr_map_data_acpi *daa;
 #endif
 	u_int irq;
 	bool ppi;
@@ -1189,6 +1217,17 @@ do_gicv5_map_intr(device_t dev, struct intr_map_data *data, bool *ppip,
 		    &trig) != 0)
 			return (EINVAL);
 		break;
+#endif
+#ifdef DEV_ACPI
+	case INTR_MAP_DATA_ACPI:
+		daa = (struct intr_map_data_acpi *)data;
+		if ((daa->irq & GSI_INT_TYPE_MASK) == GSI_INT_TYPE_IWB)
+			return (EINVAL);
+		irq = daa->irq & GSI_INT_ID_MASK;
+		pol = daa->pol;
+		trig = daa->trig;
+		ppi = ((daa->irq & GSI_INT_TYPE_MASK) == GSI_INT_TYPE_PPI);
+		break;
 #endif
 	default:
 		return (EINVAL);
@@ -1231,11 +1270,22 @@ static int
 gicv5_map_intr(device_t dev, struct intr_map_data *data,
     struct intr_irqsrc **isrcp)
 {
+#ifdef DEV_ACPI
+	struct intr_map_data_acpi *daa;
+#endif
 	struct gicv5_softc *sc;
 	u_int irq;
 	int error;
 	bool ppi;
 
+#ifdef DEV_ACPI
+	if (data->type == INTR_MAP_DATA_ACPI) {
+		daa = (struct intr_map_data_acpi *)data;
+		if ((daa->irq & GSI_INT_TYPE_MASK) == GSI_INT_TYPE_IWB)
+			return (do_gicv5_map_iwb_intr(data, isrcp));
+	}
+#endif
+
 	error = do_gicv5_map_intr(dev, data, &ppi, &irq, NULL, NULL);
 	if (error == 0) {
 		sc = device_get_softc(dev);
diff --git a/sys/arm64/arm64/gicv5_acpi.c b/sys/arm64/arm64/gicv5_acpi.c
new file mode 100644
index 000000000000..95ae488e3ed2
--- /dev/null
+++ b/sys/arm64/arm64/gicv5_acpi.c
@@ -0,0 +1,455 @@
+/*-
+ * Copyright (c) 2026 Arm Ltd
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_acpi.h"
+
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/cpuset.h>
+
+#include <machine/intr.h>
+#include <machine/resource.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+
+#include <arm/arm/gic_common.h>
+#include "gicv5var.h"
+
+struct gic_v5_acpi_devinfo {
+	struct gicv5_devinfo	di_base;
+};
+
+static device_identify_t gic_v5_acpi_identify;
+static device_probe_t gic_v5_acpi_probe;
+static device_attach_t gic_v5_acpi_attach;
+static bus_get_resource_list_t gic_v5_acpi_get_resource_list;
+
+static void gic_v5_acpi_bus_attach(device_t);
+
+static device_method_t gic_v5_acpi_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_identify,		gic_v5_acpi_identify),
+	DEVMETHOD(device_probe,			gic_v5_acpi_probe),
+	DEVMETHOD(device_attach,		gic_v5_acpi_attach),
+
+	/* Bus interface */
+	DEVMETHOD(bus_get_resource_list,	gic_v5_acpi_get_resource_list),
+
+	/* End */
+	DEVMETHOD_END
+};
+
+DEFINE_CLASS_1(gic, gic_v5_acpi_driver, gic_v5_acpi_methods,
+    sizeof(struct gicv5_softc), gicv5_driver);
+
+EARLY_DRIVER_MODULE(gic_v5, acpi, gic_v5_acpi_driver, 0, 0,
+    BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
+
+struct madt_table_data {
+	device_t parent;
+	device_t dev;
+	ACPI_MADT_GICV5_IRS *irs;
+	int count;
+};
+
+#define IRS_REGISTER_SIZE 0x10000
+#define ITS_REGISTER_SIZE 0x10000
+#define ITS_TRANSLATE_REGISTER_SIZE 0x10000
+#define IWB_REGISTER_SIZE 0x10000
+
+static void
+madt_handler(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	struct madt_table_data *madt_data;
+	ACPI_MADT_GICV5_IRS *irs;
+
+	madt_data = (struct madt_table_data *)arg;
+
+	switch (entry->Type) {
+	case ACPI_MADT_TYPE_GICV5_IRS:
+		if (madt_data->irs) {
+			if (bootverbose)
+				device_printf(madt_data->parent,
+				    "gic: Already have an IRS");
+			break;
+		}
+
+		irs = (ACPI_MADT_GICV5_IRS *)entry;
+
+		if (irs->Version != 5)
+			break;
+
+		madt_data->irs = irs;
+		madt_data->count++;
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void
+rdist_map(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	ACPI_MADT_GICV5_IRS *irs;
+
+	struct madt_table_data *madt_data;
+
+	madt_data = (struct madt_table_data *)arg;
+
+	switch (entry->Type) {
+	case ACPI_MADT_TYPE_GICV5_IRS:
+		if (madt_data->irs) {
+			if (bootverbose)
+				device_printf(madt_data->parent,
+				    "gic: Already have an IRS");
+			break;
+		}
+
+		irs = (ACPI_MADT_GICV5_IRS *)entry;
+
+		if (irs->Version != 5)
+			break;
+
+		madt_data->irs = irs;
+
+		BUS_SET_RESOURCE(madt_data->parent, madt_data->dev,
+		    SYS_RES_MEMORY, madt_data->count, irs->ConfigBaseAddress,
+		    IRS_REGISTER_SIZE);
+		madt_data->count++;
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void
+gic_v5_acpi_identify(driver_t *driver, device_t parent)
+{
+	struct madt_table_data madt_data;
+	ACPI_TABLE_MADT *madt;
+	vm_paddr_t physaddr;
+	uintptr_t private;
+	device_t dev;
+
+	physaddr = acpi_find_table(ACPI_SIG_MADT);
+	if (physaddr == 0)
+		return;
+
+	madt = acpi_map_table(physaddr, ACPI_SIG_MADT);
+	if (madt == NULL) {
+		device_printf(parent, "gic: Unable to map the MADT\n");
+		return;
+	}
+
+	madt_data.parent = parent;
+	madt_data.count = 0;
+	madt_data.irs = NULL;
+	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
+	    madt_handler, &madt_data);
+	if (madt_data.count == 0) {
+		device_printf(parent, "No GICv5 IRS\n");
+		goto out;
+	}
+
+	dev = BUS_ADD_CHILD(parent, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE,
+	    "gic", -1);
+	if (dev == NULL) {
+		device_printf(parent, "add gic child failed\n");
+		goto out;
+	}
+
+	/* Add the MADT data */
+	private = madt_data.irs->Version;
+	acpi_set_private(dev, (void *)private);
+
+	madt_data.dev = dev;
+	madt_data.count = 0;
+	madt_data.irs = NULL;
+	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
+	    rdist_map, &madt_data);
+
+out:
+	acpi_unmap_table(madt);
+}
+
+static int
+gic_v5_acpi_probe(device_t dev)
+{
+	switch ((uintptr_t)acpi_get_private(dev)) {
+	case 5:
+		break;
+	default:
+		return (ENXIO);
+	}
+
+	device_set_desc(dev, "ARM Generic Interrupt Controller v5");
+	return (BUS_PROBE_NOWILDCARD);
+}
+
+static void
+madt_count_irs(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	struct gicv5_softc *sc = arg;
+
+	if (entry->Type == ACPI_MADT_TYPE_GICV5_IRS)
+		sc->gic_nirs++;
+}
+
+static int
+gic_v5_acpi_count_nirs(struct gicv5_softc *sc, ACPI_TABLE_MADT *madt)
+{
+	sc->gic_nirs = 0;
+
+	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
+	    madt_count_irs, sc);
+
+	return (sc->gic_nirs > 0 ? 0 : ENXIO);
+}
+
+struct process_gicc_args {
+	struct gicv5_softc *sc;
+	ACPI_TABLE_MADT *madt;
+	uint32_t irs_id;
+	cpuset_t cpuset;
+};
+
+struct process_irs_args {
+	device_t dev;
+	struct gicv5_softc *sc;
+	ACPI_TABLE_MADT *madt;
+};
+
+static void
+madt_process_gicc(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	struct process_gicc_args *gicc_args;
+	ACPI_MADT_GENERIC_INTERRUPT *gicc;
+
+	gicc_args = (struct process_gicc_args *)arg;
+
+	if (entry->Type == ACPI_MADT_TYPE_GENERIC_INTERRUPT) {
+		gicc = (ACPI_MADT_GENERIC_INTERRUPT *)entry;
+
+		if (gicc->IrsId == gicc_args->irs_id)
+			CPU_SET(gicc->CpuInterfaceNumber, &gicc_args->cpuset);
+	}
+}
+
+static void
+madt_process_irs(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	struct process_gicc_args gicc_args;
+	struct process_irs_args *irs_args;
+	ACPI_MADT_GICV5_IRS *irs;
+	struct gicv5_softc *sc;
+	ACPI_TABLE_MADT *madt;
+	int error;
+
+	irs_args = (struct process_irs_args *)arg;
+	sc = irs_args->sc;
+	madt = irs_args->madt;
+
+	if (entry->Type == ACPI_MADT_TYPE_GICV5_IRS) {
+		irs = (ACPI_MADT_GICV5_IRS *)entry;
+
+		gicc_args.sc = sc;
+		gicc_args.madt = madt;
+		gicc_args.irs_id = irs->IrsId;
+		CPU_ZERO(&gicc_args.cpuset);
+
+		/* Build gicc_args.cpuset from GICC tables */
+		acpi_walk_subtables(madt + 1,
+		    (char *)madt + madt->Header.Length,
+		    madt_process_gicc, &gicc_args);
+
+		sc->gic_nirs++;
+
+		gicv5_irs_init(irs_args->dev, sc->gic_nirs - 1,
+		    &gicc_args.cpuset);
+
+		error = bus_set_resource(irs_args->dev, SYS_RES_MEMORY,
+		    sc->gic_nirs - 1, irs->ConfigBaseAddress,
+		    IRS_REGISTER_SIZE);
+		if (error != 0)
+			panic("%s: Unable to set memory resourec",
+			    device_get_nameunit(irs_args->dev));
+	}
+}
+
+static int
+gic_v5_acpi_process_irs(device_t dev, struct gicv5_softc *sc,
+    ACPI_TABLE_MADT *madt)
+{
+	struct process_irs_args args;
+
+	args.dev = dev;
+	args.sc = sc;
+	args.madt = madt;
+
+	sc->gic_nirs = 0;
+
+	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
+	    madt_process_irs, &args);
+
+	return (0);
+}
+
+static int
+gic_v5_acpi_attach(device_t dev)
+{
+	struct gicv5_softc *sc;
+	ACPI_TABLE_MADT *madt;
+	vm_paddr_t physaddr;
+	int err;
+
+	sc = device_get_softc(dev);
+	sc->gic_dev = dev;
+	sc->gic_bus = GIC_BUS_ACPI;
+
+	physaddr = acpi_find_table(ACPI_SIG_MADT);
+	if (physaddr == 0)
+		return (ENXIO);
+
+	madt = acpi_map_table(physaddr, ACPI_SIG_MADT);
+	if (madt == NULL) {
+		device_printf(dev, "Unable to map the MADT\n");
+		return (ENXIO);
+	}
+
+	err = gic_v5_acpi_count_nirs(sc, madt);
+	if (err != 0)
+		goto count_error;
+
+	sc->gic_irs = mallocarray(sc->gic_nirs, sizeof(sc->gic_irs[0]),
+	    M_DEVBUF, M_WAITOK | M_ZERO);
+
+	// for each IRS
+	//   walk GICC entries to find matching IRS IDs
+	err = gic_v5_acpi_process_irs(dev, sc, madt);
+
+	gicv5_attach(dev);
+
+	sc->gic_pic = intr_pic_register(dev, ACPI_INTR_XREF);
+	if (sc->gic_pic == NULL)
+		panic("%s: could not register PIC", device_get_nameunit(dev));
+
+	intr_ipi_pic_register(dev, 0);
+
+	err = intr_pic_claim_root(dev, ACPI_INTR_XREF, gicv5_intr, sc,
+	    INTR_ROOT_IRQ);
+	if (err != 0)
+		panic("%s: Unable to claim PIC root", device_get_nameunit(dev));
+
+	gic_v5_acpi_bus_attach(dev);
+
+	bus_attach_children(dev);
+
+	acpi_unmap_table(madt);
+
+	return (0);
+
+count_error:
+	if (bootverbose) {
+		device_printf(dev,
+		    "Failed to attach. Error %d\n", err);
+	}
+
+	acpi_unmap_table(madt);
+
+	return (err);
+}
+
+static void
+gic_v5_add_children(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	ACPI_MADT_GICv5_ITS *its;
+	struct gic_v5_acpi_devinfo *di;
+	struct gicv5_softc *sc;
+	device_t dev;
+
+	if (entry->Type == ACPI_MADT_TYPE_GICV5_ITS) {
+		/* We have an ITS, add it as a child */
+		its = (ACPI_MADT_GICv5_ITS *)entry;
+		dev = arg;
+		sc = device_get_softc(dev);
+
+		di = malloc(sizeof(*di), M_DEVBUF, M_WAITOK | M_ZERO);
+
+		resource_list_init(&di->di_base.di_rl);
+		resource_list_add(&di->di_base.di_rl, SYS_RES_MEMORY, 0,
+		    its->BaseAddress, its->BaseAddress + ITS_REGISTER_SIZE - 1,
+		    ITS_REGISTER_SIZE);
+
+		di->di_base.di_irs = sc->gic_irs[0];
+
+		gicv5_add_child(dev, &di->di_base);
+
+	}
+}
+
+static void
+gic_v5_acpi_bus_attach(device_t dev)
+{
+	ACPI_TABLE_MADT *madt;
+	vm_paddr_t physaddr;
+
+	physaddr = acpi_find_table(ACPI_SIG_MADT);
+	if (physaddr == 0) {
+		device_printf(dev, "gic_v5_acpi_bus_attach: no madt\n");
+		return;
+	}
+
+	madt = acpi_map_table(physaddr, ACPI_SIG_MADT);
+	if (madt == NULL) {
+		device_printf(dev, "Unable to map the MADT to add children\n");
+		return;
+	}
+
+	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
+	    gic_v5_add_children, dev);
+
+	acpi_unmap_table(madt);
+
+	bus_attach_children(dev);
+}
+
+static struct resource_list *
+gic_v5_acpi_get_resource_list(device_t bus, device_t child)
+{
+	struct gic_v5_acpi_devinfo *di;
+
+	di = device_get_ivars(child);
+	KASSERT(di != NULL, ("%s: No devinfo", __func__));
+
+	return (&di->di_base.di_rl);
+}
diff --git a/sys/arm64/arm64/gicv5_its.c b/sys/arm64/arm64/gicv5_its.c
index 73395c2eb05b..f69ab215f9e7 100644
--- a/sys/arm64/arm64/gicv5_its.c
+++ b/sys/arm64/arm64/gicv5_its.c
@@ -56,6 +56,11 @@
 #include <dev/ofw/ofw_subr.h>
 #endif
 
+#ifdef DEV_ACPI
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+#endif
+
 #include <dev/pci/pcireg.h>
 #include <dev/pci/pcivar.h>
 
@@ -1237,3 +1242,136 @@ gicv5_its_fdt_attach(device_t dev)
 	return (gicv5_its_attach(dev));
 }
 #endif /* FDT */
+
+#ifdef DEV_ACPI
+static int
+gicv5_its_acpi_probe(device_t dev);
+static int
+gicv5_its_acpi_attach(device_t dev);
+
+static device_method_t gicv5_its_acpi_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,			gicv5_its_acpi_probe),
+	DEVMETHOD(device_attach,		gicv5_its_acpi_attach),
+
+	/* End */
+	DEVMETHOD_END
+};
+
+#define its_baseclasses itsv5_acpi_baseclasses
+DEFINE_CLASS_1(its, gicv5_its_acpi_driver, gicv5_its_acpi_methods,
+    sizeof(struct gicv5_its_softc), gicv5_its_driver);
+#undef its_baseclasses
+
+EARLY_DRIVER_MODULE(itsv5_acpi, gic, gicv5_its_acpi_driver, 0, 0,
+    BUS_PASS_INTERRUPT + BUS_PASS_ORDER_MIDDLE);
+
+static int
+gicv5_its_acpi_probe(device_t dev)
+{
+	device_t parent = device_get_parent(dev);
+
+	if (!parent)
+		return (ENXIO);
+
+	if (strcmp(device_get_name(parent), "gic"))
+		return (ENXIO);
+
+	device_set_desc(dev, "ARM GICv5 Interrupt Translation Service");
+	return (BUS_PROBE_DEFAULT);
+}
+
+struct find_its_translate_data {
+	bus_addr_t its_frame_paddr;
+	uint8_t flags;
+	int its_found;
+	int its_translate_found;
+	u_int its_translate_xref;
+	int its_translate_pxm;
+};
+
+static void
+find_its_translate(ACPI_SUBTABLE_HEADER *entry, void *arg)
+{
+	ACPI_MADT_GICv5_ITS_TRANSLATE *its_translate;
+	ACPI_MADT_GICv5_ITS *its;
+	struct find_its_translate_data *find_data;
+	int err;
+
+	find_data = (struct find_its_translate_data *)arg;
+
+	switch (entry->Type) {
+	case ACPI_MADT_TYPE_GICV5_ITS:
+		its = (ACPI_MADT_GICv5_ITS *)entry;
+
+		find_data->its_found++;
+		find_data->flags = its->Flags;
+		break;
+
+	case ACPI_MADT_TYPE_GICV5_ITS_TRANSLATE:
+		its_translate = (ACPI_MADT_GICv5_ITS_TRANSLATE *)entry;
+
+		err = acpi_iort_its_lookup(its_translate->TranslateFrameId,
+		    &find_data->its_translate_xref,
+		    &find_data->its_translate_pxm);
+		if (err != 0)
+			break;
+
+		find_data->its_translate_found++;
+		find_data->its_frame_paddr = its_translate->BaseAddress;
+		break;
+	}
+}
+
+static int
+gicv5_its_acpi_attach(device_t dev)
+{
+	struct find_its_translate_data find_data;
+	struct gicv5_its_softc *sc;
+	ACPI_TABLE_MADT *madt;
+	vm_paddr_t physaddr;
+
+	sc = device_get_softc(dev);
+
+	physaddr = acpi_find_table(ACPI_SIG_MADT);
+	if (physaddr == 0)
+		return (ENODEV);
+
+	madt = acpi_map_table(physaddr, ACPI_SIG_MADT);
+	if (madt == NULL) {
+		device_printf(dev, "gicv5_its: Unable to map the MADT\n");
+		return (ENODEV);
+	}
+
+	find_data.its_found = 0;
+	find_data.its_translate_found = 0;
+
+	acpi_walk_subtables(madt + 1, (char *)madt + madt->Header.Length,
+	    find_its_translate, &find_data);
+
+	if (!find_data.its_found) {
+		device_printf(dev, "No valid ITS found\n");
+		return (EINVAL);
+	}
+	if (find_data.its_found > 1) {
+		device_printf(dev, "Multiple ITS found\n");
+		return (EINVAL);
+	}
+	if (!find_data.its_translate_found) {
+		device_printf(dev, "No valid ITS frame found\n");
+		return (EINVAL);
+	}
+	if (find_data.its_translate_found > 1) {
+		device_printf(dev, "Multiple ITS frames found\n");
+		return (EINVAL);
+	}
+
+	sc->its_frame.its_frame_paddr = find_data.its_frame_paddr;
+	sc->its_frame.its_pic = NULL;
+	sc->its_frame.its_xref = find_data.its_translate_xref;
+	sc->its_coherent = !(find_data.flags &
+	    ACPI_MADT_GICV5_ITS_NON_COHERENT);
+
+	return (gicv5_its_attach(dev));
+}
+#endif /* DEV_ACPI */
diff --git a/sys/arm64/arm64/gicv5_iwb.c b/sys/arm64/arm64/gicv5_iwb.c
index fa2ad370e626..2cf4d9723961 100644
--- a/sys/arm64/arm64/gicv5_iwb.c
+++ b/sys/arm64/arm64/gicv5_iwb.c
@@ -47,9 +47,17 @@
 #include <dev/ofw/ofw_bus_subr.h>
 #endif
 
+#ifdef DEV_ACPI
+#include <contrib/dev/acpica/include/acpi.h>
+#include <dev/acpica/acpivar.h>
+#endif
+
 #include "pci_if.h"
 #include "pic_if.h"
 
+#include "gicv5reg.h"
+#include "gicv5var.h"
+
 #define	IWB_IDR0		0x0000
 #define	 IDR0_IW_RANGE_SHIFT	0
 #define	 IDR0_IW_RANGE_MASK	(0x7ffu << IDR0_IW_RANGE_SHIFT)
@@ -207,6 +215,8 @@ gicv5_iwb_attach(device_t dev)
 	error = PCI_ALLOC_MSI(device_get_parent(dev), dev, &count);
 	if (error != 0) {
 		device_printf(dev, "Unable to allocate MSI interrupts\n");
+		bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
+		    sc->sc_mem);
 		return (error);
 	}
 
@@ -362,6 +372,9 @@ do_gicv5_iwb_map_intr(device_t dev, struct intr_map_data *data, u_int *irqp,
 	enum intr_trigger trig;
 #ifdef FDT
 	struct intr_map_data_fdt *daf;
+#endif
+#ifdef DEV_ACPI
+	struct intr_map_data_acpi *daa;
 #endif
 	u_int irq;
 
@@ -375,6 +388,14 @@ do_gicv5_iwb_map_intr(device_t dev, struct intr_map_data *data, u_int *irqp,
 		    &trig) != 0)
 			return (EINVAL);
 		break;
+#endif
+#ifdef DEV_ACPI
+	case INTR_MAP_DATA_ACPI:
+		daa = (struct intr_map_data_acpi *)data;
+		irq = daa->irq & GSI_IWB_INT_ID_MASK;
+		pol = daa->pol;
+		trig = daa->trig;
+		break;
 #endif
 	default:
 		return (EINVAL);
@@ -559,3 +580,75 @@ gicv5_iwb_fdt_attach(device_t dev)
 	return (0);
 }
 #endif /* FDT */
+
+#ifdef DEV_ACPI
+struct gicv5_iwb_acpi_softc {
+	struct gicv5_iwb_softc sc_base;
+};
+
+static device_probe_t gicv5_iwb_acpi_probe;
+static device_attach_t gicv5_iwb_acpi_attach;
+
+static device_method_t gicv5_iwb_acpi_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,			gicv5_iwb_acpi_probe),
+	DEVMETHOD(device_attach,		gicv5_iwb_acpi_attach),
+
+	/* End */
+	DEVMETHOD_END
+};
+
+#define iwb_baseclasses iwbv5_acpi_baseclasses
+DEFINE_CLASS_1(iwb, gicv5_iwb_acpi_driver, gicv5_iwb_acpi_methods,
+    sizeof(struct gicv5_iwb_acpi_softc), gicv5_iwb_driver);
+#undef iwb_baseclasses
+
+/* This needs to be after the ITS as it sends MSI messages there */
+EARLY_DRIVER_MODULE(gicv5_iwb_acpi, acpi, gicv5_iwb_acpi_driver, 0, 0,
+    BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);
+
+static int
+gicv5_iwb_acpi_probe(device_t dev)
+{
+	ACPI_HANDLE h;
+
+	if ((h = acpi_get_handle(dev)) == NULL)
+		return (ENXIO);
+
+	if (!acpi_MatchHid(h, "ARMH0003"))
+		return (ENXIO);
+
+	device_set_desc(dev, "ARM GICv5 Interrupt Wire Bridge");
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+gicv5_iwb_acpi_attach(device_t dev)
+{
+	struct gicv5_iwb_acpi_softc *sc;
+	u_int xref;
+	int its_id;
+	int error;
+	int pxm;
+
+	sc = device_get_softc(dev);
+
+	error = acpi_iort_lookup_its_from_iwb(dev, &its_id);
+	if (error != 0)
+		return (error);
+
+	error = acpi_iort_its_lookup(its_id, &xref, &pxm);
+	if (error != 0)
+		return (error);
+
+	error = gicv5_iwb_attach(dev);
+	if (error != 0)
+		return (error);
+
+	sc->sc_base.sc_pic = intr_pic_register(dev, xref);
+	if (sc->sc_base.sc_pic == NULL)
+		return (ENXIO);
+
+	return (0);
+}
+#endif /* ACPI */
diff --git a/sys/arm64/arm64/gicv5var.h b/sys/arm64/arm64/gicv5var.h
index 027bf2542521..faab72387668 100644
--- a/sys/arm64/arm64/gicv5var.h
+++ b/sys/arm64/arm64/gicv5var.h
@@ -68,4 +68,6 @@ int gicv5_intr(void *);
 void gicv5_irs_init(device_t, u_int, cpuset_t *);
 void gicv5_irs_extend_ist(device_t, device_t, u_int);
 
+void gicv5_attach_iwb(device_t, device_t);
+
 #endif /* _ARM64_GICV5VAR_H_ */
diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64
index 97268193d482..7e604693eba8 100644
--- a/sys/conf/files.arm64
+++ b/sys/conf/files.arm64
@@ -60,9 +60,10 @@ arm64/arm64/gic_v3.c				standard
 arm64/arm64/gic_v3_acpi.c			optional acpi
 arm64/arm64/gic_v3_fdt.c			optional fdt
 arm64/arm64/gicv5.c				standard
+arm64/arm64/gicv5_acpi.c			optional acpi
 arm64/arm64/gicv5_fdt.c				optional fdt
-arm64/arm64/gicv5_its.c				optional fdt
-arm64/arm64/gicv5_iwb.c				optional fdt
+arm64/arm64/gicv5_its.c				standard
+arm64/arm64/gicv5_iwb.c				standard
 arm64/arm64/hyp_stub.S				standard
 arm64/arm64/identcpu.c				standard
 arm64/arm64/kexec_support.c			standard