Subject: [BOOTWRAPPER PATCH v3 2/2] Add support for GICv5

From: Vladimir Murzin <[email-masked]>
Date: Fri, 29 May 2026 10:50:32 +0100

Performs the minimal initialization required for GICv5 support. GICv5
support can be requested with --with-gic=v5.

Signed-off-by: Vladimir Murzin <[email-masked]>
Tested-by: Sascha Bischoff <[email-masked]>
---
 Makefile.am                    |   7 ++
 arch/aarch64/include/asm/cpu.h |  11 +++
 common/gic-v5.c                | 132 +++++++++++++++++++++++++++++++++
 configure.ac                   |   7 +-
 scripts/FDT.pm                 |  16 ++++
 scripts/findbase-by-regname.pl |  44 +++++++++++
 6 files changed, 214 insertions(+), 3 deletions(-)
 create mode 100644 common/gic-v5.c
 create mode 100755 scripts/findbase-by-regname.pl

diff --git a/Makefile.am b/Makefile.am
index 2710494..aacd639 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -82,6 +82,13 @@ PSCI_NODE	:=
 CPU_NODES	:=
 endif
 
+if GICV5
+GIC_IRS_BASE	:= $(shell perl -I $(SCRIPT_DIR) $(SCRIPT_DIR)/findbase-by-regname.pl $(KERNEL_DTB) "el3-config" 'arm,gic-v5-irs')
+GIC_IWB_BASE	:= $(shell perl -I $(SCRIPT_DIR) $(SCRIPT_DIR)/findbase.pl $(KERNEL_DTB) 0 'arm,gic-v5-iwb')
+DEFINES		+= -DGIC_IRS_BASE=$(GIC_IRS_BASE)
+DEFINES		+= -DGIC_IWB_BASE=$(GIC_IWB_BASE)
+endif
+
 if GICV3
 GIC_DIST_BASE	:= $(shell perl -I $(SCRIPT_DIR) $(SCRIPT_DIR)/findbase.pl $(KERNEL_DTB) 0 'arm,gic-v3')
 GIC_RDIST_BASE	:= $(shell perl -I $(SCRIPT_DIR) $(SCRIPT_DIR)/findbase.pl $(KERNEL_DTB) 1 'arm,gic-v3')
diff --git a/arch/aarch64/include/asm/cpu.h b/arch/aarch64/include/asm/cpu.h
index ac50474..af4191c 100644
--- a/arch/aarch64/include/asm/cpu.h
+++ b/arch/aarch64/include/asm/cpu.h
@@ -128,6 +128,7 @@
 #define ID_AA64PFR1_EL1_THE		BITS(51, 48)
 
 #define ID_AA64PFR2_EL1			s3_0_c0_c4_2
+#define ID_AA64PFR2_EL1_GCIE		BITS(15, 12)
 #define ID_AA64PFR2_EL1_FPMR		BITS(35, 32)
 
 #define ID_AA64SMFR0_EL1		s3_0_c0_c4_5
@@ -169,6 +170,11 @@
 #define ICC_CTLR_EL3		S3_6_C12_C12_4
 #define ICC_PMR_EL1		S3_0_C4_C6_0
 
+#define ICC_PPI_DOMAINR0_EL3	S3_6_C12_C8_4
+#define ICC_PPI_DOMAINR1_EL3	S3_6_C12_C8_5
+#define ICC_PPI_DOMAINR2_EL3	S3_6_C12_C8_6
+#define ICC_PPI_DOMAINR3_EL3	S3_6_C12_C8_7
+
 #define VSTCR_EL2		s3_4_c2_c6_2
 #define VSCTLR_EL2		s3_4_c2_c0_0
 
@@ -245,6 +251,11 @@ static inline int has_gicv3_sysreg(void)
 	return !!mrs_field(ID_AA64PFR0_EL1, GIC);
 }
 
+static inline int has_gicv5_sysreg(void)
+{
+	return !!mrs_field(ID_AA64PFR2_EL1, GCIE);
+}
+
 #endif /* !__ASSEMBLY__ */
 
 #endif
diff --git a/common/gic-v5.c b/common/gic-v5.c
new file mode 100644
index 0000000..49b7b0e
--- /dev/null
+++ b/common/gic-v5.c
@@ -0,0 +1,132 @@
+/*
+ * gic-v5.c
+ *
+ * Copyright (C) 2025 ARM Limited. All rights reserved.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE.txt file.
+ */
+
+#include <stdint.h>
+
+#include <cpu.h>
+#include <gic.h>
+#include <asm/io.h>
+
+#define IWB_IDR0			0x0
+#define IWB_IDR0_IW_RANGE_SHIFT		0x0
+#define IWB_IDR0_IW_RANGE_MASK		0x7ff
+
+#define IWB_CR0				0x80
+#define IWB_CR0_IWBEN			(1 << 0)
+#define IWB_CR0_IDLE			(1 << 1)
+
+#define IWB_WENABLE_STATUSR		0xc0
+#define IWB_WENABLE_STATUSR_IDLE	(1 << 0)
+
+#define IWB_WDOMAIN_STATUSR		0xc4
+#define IWB_WDOMAIN_STATUSR_IDLE	(1 << 0)
+
+#define IWB_WENABLER			0x2000
+#define IWB_WDOMAINR			0x8000
+
+#define IRS_IDR6			0x0018
+#define IRS_IDR6_SPI_IRS_RANGE_MASK	0x1ffffff
+
+#define IRS_IDR7			0x001c
+#define IRS_IDR7_SPI_BASE_MASK		0xffffff
+
+#define IRS_SPI_SELR			0x108
+#define IRS_SPI_DOMAINR			0x10c
+
+#define IRS_SPI_STATUSR			0x0118
+#define IRS_SPI_STATUSR_IDLE		(1 << 0)
+
+static void gic_iwb_init(void) {
+	void *iwb_ptr = (void *)GIC_IWB_BASE;
+	unsigned int num;
+	unsigned int i;
+
+	/* Get number of implemented wire control registers */
+	num = ((raw_readl(iwb_ptr + IWB_IDR0) >> IWB_IDR0_IW_RANGE_SHIFT) & IWB_IDR0_IW_RANGE_MASK) + 1;
+
+	/* Disable all wires */
+	for (i = 0; i < num; i++)
+		raw_writel(0, iwb_ptr + IWB_WENABLER + i * 4);
+
+	while (!(raw_readl(iwb_ptr + IWB_WENABLE_STATUSR) & IWB_WENABLE_STATUSR_IDLE));
+
+	/* Assign all wires to Non-Secure domain */
+	for (i = 0; i < num * 2; i++)
+		raw_writel(0x55555555, iwb_ptr + IWB_WDOMAINR + i * 4);
+
+	while (!(raw_readl(iwb_ptr + IWB_WDOMAIN_STATUSR) & IWB_WDOMAIN_STATUSR_IDLE));
+
+	/* Enable IWB */
+	raw_writel(IWB_CR0_IWBEN, iwb_ptr + IWB_CR0);
+
+	while (!(raw_readl(iwb_ptr + IWB_CR0) & IWB_CR0_IDLE));
+}
+
+static void gic_irs_init(void) {
+	void *irs_ptr = (void *)GIC_IRS_BASE;
+	unsigned int range;
+	unsigned int base;
+	unsigned int i;
+
+	/* Get the range of implemented SPIs */
+	base = raw_readl(irs_ptr + IRS_IDR7) & IRS_IDR7_SPI_BASE_MASK;
+	range = raw_readl(irs_ptr + IRS_IDR6) & IRS_IDR6_SPI_IRS_RANGE_MASK;
+
+	for (i = base; i < base + range; i++) {
+		/* Select SPI */
+		raw_writel(i, irs_ptr + IRS_SPI_SELR);
+		while (!(raw_readl(irs_ptr + IRS_SPI_STATUSR) & IRS_SPI_STATUSR_IDLE));
+
+		/* Assign SPI to Non-Secure domain */
+		raw_writel(1, irs_ptr + IRS_SPI_DOMAINR);
+		while (!(raw_readl(irs_ptr + IRS_SPI_STATUSR) & IRS_SPI_STATUSR_IDLE));
+	}
+}
+
+static void gic_ppi_init(void) {
+	uint64_t val = 0;
+
+	val |= 1UL << (2 * 31); // Trace Buffer Unit
+	val |= 1UL << (2 * 30); // EL1 Physical Timer
+	val |= 1UL << (2 * 28); // Non-secure EL2 Virtual Timer
+	val |= 1UL << (2 * 27); // EL1 Virtual Timer
+	val |= 1UL << (2 * 26); // Non-secure EL2 Physical Timer
+	val |= 1UL << (2 * 25); // GIC maintenance interrupt
+	val |= 1UL << (2 * 24); // Generic CTI interrupt trigger event
+	val |= 1UL << (2 * 23); // PMU overflow interrupt request
+	val |= 1UL << (2 * 22); // Debug communication channel
+	val |= 1UL << (2 * 21); // Profiling Buffer management interrupt request
+	val |= 1UL << (2 * 15); // Hardware accelerator for cleaning Dirty state interrupt
+	val |= 1UL << (2 * 3);  // Reserved for software usage
+
+	/* Assign PPI to Non-Secure domain */
+	msr(ICC_PPI_DOMAINR0_EL3, val);
+	isb();
+}
+
+void gic_secure_init(void)
+{
+	/*
+	 * If GICv5 is not available, skip initialisation. The OS will probably
+	 * fail with a warning, but this should be easier to debug than a
+	 * failure within the boot wrapper.
+	 */
+	if (!has_gicv5_sysreg())
+		return;
+
+	if (this_cpu_logical_id() == 0) {
+		gic_iwb_init();
+		gic_irs_init();
+	}
+
+	gic_ppi_init();
+
+	return;
+}
+
diff --git a/configure.ac b/configure.ac
index 6f486c4..f4faff7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -141,18 +141,19 @@ AC_SUBST([XEN_CMDLINE], [$X_CMDLINE])

 AC_ARG_WITH([gic],
-  AS_HELP_STRING([--with-gic={v2|v3}], [select GIC version]),
+  AS_HELP_STRING([--with-gic={v2|v3|v5}], [select GIC version]),
   [GIC_VERSION=$withval],
   [GIC_VERSION=v2])
 
 AS_CASE([$GIC_VERSION],
-  [v2|v3], [],
-  [AC_MSG_ERROR([Invalid GIC version: $GIC_VERSION (use v2 or v3)])])
+  [v2|v3|v5], [],
+  [AC_MSG_ERROR([Invalid GIC version: $GIC_VERSION (use v2, v3, or v5)])])
 
 AC_SUBST([GIC_VERSION], [$GIC_VERSION])
 
 AM_CONDITIONAL([GICV2], [test "x$GIC_VERSION" = "xv2"])
 AM_CONDITIONAL([GICV3], [test "x$GIC_VERSION" = "xv3"])
+AM_CONDITIONAL([GICV5], [test "x$GIC_VERSION" = "xv5"])

 # Ensure that we have all the needed programs
diff --git a/scripts/FDT.pm b/scripts/FDT.pm
index 9adf70b..3f49ba6 100755
--- a/scripts/FDT.pm
+++ b/scripts/FDT.pm
@@ -322,6 +322,22 @@ sub get_num_reg_cells
 	return ($ac, $sc);
 }
 
+sub get_regname_idx
+{
+    my $self = shift;
+    my $regname = shift;
+
+    my $prop = $self->get_property("reg-names");
+
+    return undef if (not defined($prop));
+
+    my @names = $prop->read_strings();
+
+    my ($idx) = grep { $names[$_] eq $regname } 0 .. $#names;
+
+    return $idx;
+}
+
 sub translate_address
 {
 	my $self = shift;
diff --git a/scripts/findbase-by-regname.pl b/scripts/findbase-by-regname.pl
new file mode 100755
index 0000000..49cd0ce
--- /dev/null
+++ b/scripts/findbase-by-regname.pl
@@ -0,0 +1,44 @@
+#!/usr/bin/perl -w
+# Find device register base addresses.
+#
+# Usage: ./$0 <DTB> <regname> <compatible ...>
+#
+# Copyright (C) 2026 ARM Limited. All rights reserved.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE.txt file.
+
+use warnings;
+use strict;
+
+use FDT;
+
+my $filename = shift;
+die("No filename provided") unless defined($filename);
+
+my $regname = shift;
+die("no reg regname provided") unless defined($regname);
+
+my @compats = shift;
+
+open (my $fh, "<:raw", $filename) or die("Unable to open file '$filename'");
+
+my $fdt = FDT->parse($fh) or die("Unable to parse DTB");
+
+my $root = $fdt->get_root();
+
+my @devs = ();
+for my $compat (@compats) {
+	push @devs, $root->find_compatible($compat);
+}
+
+# We only care about finding the first matching device
+my $dev = shift @devs;
+die("No matching devices found") if (not defined($dev));
+
+my $idx = $dev->get_regname_idx($regname);
+die("Cannot find reg name $regname") if (not defined($idx));
+my ($addr, $size) = $dev->get_translated_reg($idx);
+die("Cannot find reg entry $idx") if (not defined($addr) or not defined($size));
+
+printf("0x%016x\n", $addr);
-- 
2.34.1