feat(gicv5): add a GICv5 driver
Change-Id: I10e125c3866e50ed5adde2e4944245f47e50f2e6
Signed-off-by: Boyan Karatotev <boyan.karatotev@arm.com>
diff --git a/drivers/arm/gic/aarch64/gic_v5.c b/drivers/arm/gic/aarch64/gic_v5.c
new file mode 100644
index 0000000..0f8f5fa
--- /dev/null
+++ b/drivers/arm/gic/aarch64/gic_v5.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <assert.h>
+
+#include <arch.h>
+#include <arch_features.h>
+#include <arch_helpers.h>
+#include <drivers/arm/gic_v5.h>
+#include <platform.h>
+
+#include <platform_def.h>
+
+/*
+ * Data structure to store the GIC per CPU context before entering
+ * a suspend to powerdown (with loss of context).
+ */
+struct gicv5_pcpu_ctx {
+ /* CPU IF registers. Only those currently in use */
+ u_register_t icc_cr0_el1;
+ /* PPI registers. We don't touch handling mode. */
+ u_register_t icc_ppi_enabler[2];
+ u_register_t icc_ppi_xpendr[2];
+ u_register_t icc_ppi_priorityr[16];
+};
+
+static uintptr_t irs_base;
+static struct gicv5_pcpu_ctx cpu_ctx[PLATFORM_CORE_COUNT];
+
+/* CPU MPIDR != GICv5 IAFFID. This holds the mapping, initialised on CPU_ON. */
+static uint16_t iaffids[PLATFORM_CORE_COUNT];
+
+/* the IST is a power of 2 since that's what goes in IRS_IST_CFGR.LPI_ID_BITS */
+struct l2_iste ist[next_power_of_2(PLATFORM_CORE_COUNT) * IRQ_NUM_SGIS];
+
+static inline uint8_t log2(uint32_t num)
+{
+ return (31 - __builtin_clz(num));
+}
+
+static inline bool is_interrupt(unsigned int interrupt_id)
+{
+ unsigned int_type = EXTRACT(INT_TYPE, interrupt_id);
+
+ return int_type == INT_PPI || int_type == INT_LPI || int_type == INT_SPI;
+}
+
+static inline u_register_t read_icc_ppi_priorityrn(unsigned n)
+{
+ switch (n) {
+ case 0:
+ return read_icc_ppi_priorityr0();
+ case 1:
+ return read_icc_ppi_priorityr1();
+ case 2:
+ return read_icc_ppi_priorityr2();
+ case 3:
+ return read_icc_ppi_priorityr3();
+ case 4:
+ return read_icc_ppi_priorityr4();
+ case 5:
+ return read_icc_ppi_priorityr5();
+ case 6:
+ return read_icc_ppi_priorityr6();
+ case 7:
+ return read_icc_ppi_priorityr7();
+ case 8:
+ return read_icc_ppi_priorityr8();
+ case 9:
+ return read_icc_ppi_priorityr9();
+ case 10:
+ return read_icc_ppi_priorityr10();
+ case 11:
+ return read_icc_ppi_priorityr11();
+ case 12:
+ return read_icc_ppi_priorityr12();
+ case 13:
+ return read_icc_ppi_priorityr13();
+ case 14:
+ return read_icc_ppi_priorityr14();
+ case 15:
+ return read_icc_ppi_priorityr15();
+ default:
+ panic();
+ }
+}
+
+static inline void write_icc_ppi_priorityrn(unsigned n, u_register_t val)
+{
+ switch (n) {
+ case 0:
+ return write_icc_ppi_priorityr0(val);
+ case 1:
+ return write_icc_ppi_priorityr1(val);
+ case 2:
+ return write_icc_ppi_priorityr2(val);
+ case 3:
+ return write_icc_ppi_priorityr3(val);
+ case 4:
+ return write_icc_ppi_priorityr4(val);
+ case 5:
+ return write_icc_ppi_priorityr5(val);
+ case 6:
+ return write_icc_ppi_priorityr6(val);
+ case 7:
+ return write_icc_ppi_priorityr7(val);
+ case 8:
+ return write_icc_ppi_priorityr8(val);
+ case 9:
+ return write_icc_ppi_priorityr9(val);
+ case 10:
+ return write_icc_ppi_priorityr10(val);
+ case 11:
+ return write_icc_ppi_priorityr11(val);
+ case 12:
+ return write_icc_ppi_priorityr12(val);
+ case 13:
+ return write_icc_ppi_priorityr13(val);
+ case 14:
+ return write_icc_ppi_priorityr14(val);
+ case 15:
+ return write_icc_ppi_priorityr15(val);
+ default:
+ panic();
+ }
+}
+
+bool is_gicv5_mode(void)
+{
+ return is_feat_gcie_supported();
+}
+
+bool gicv5_is_irq_spi(unsigned int irq_num)
+{
+ return EXTRACT(INT_TYPE, irq_num) == INT_SPI;
+}
+
+void gicv5_enable_cpuif(void)
+{
+ write_icc_cr0_el1(read_icc_cr0_el1() | ICC_CR0_EL1_EN_BIT);
+ /* make sure effects are visible */
+ isb();
+}
+
+void gicv5_setup_cpuif(void)
+{
+ iaffids[platform_get_core_pos(read_mpidr_el1())] = read_icc_iaffidr_el1();
+
+ write_icc_pcr_el1(GICV5_IDLE_PRIORITY);
+
+ gicv5_enable_cpuif();
+}
+
+void gicv5_disable_cpuif(void)
+{
+ write_icc_cr0_el1(read_icc_cr0_el1() & ~ICC_CR0_EL1_EN_BIT);
+ /* make sure effects are visible */
+ isb();
+}
+
+void gicv5_save_cpuif_context(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ cpu_ctx[core_pos].icc_cr0_el1 = read_icc_cr0_el1();
+ cpu_ctx[core_pos].icc_ppi_enabler[0] = read_icc_ppi_enabler0();
+ cpu_ctx[core_pos].icc_ppi_enabler[1] = read_icc_ppi_enabler1();
+
+ cpu_ctx[core_pos].icc_ppi_xpendr[0] = read_icc_ppi_spendr0();
+ cpu_ctx[core_pos].icc_ppi_xpendr[1] = read_icc_ppi_spendr1();
+
+ for (int i = 0; i < 15; i++) {
+ cpu_ctx[core_pos].icc_ppi_priorityr[i] = read_icc_ppi_priorityrn(i);
+ }
+
+ /* Make sure all PPIs are inactive, i.e. not suspending mid interrupt */
+ assert(read_icc_ppi_sactiver0() == 0UL && read_icc_ppi_sactiver1() == 0UL);
+}
+
+void gicv5_restore_cpuif_context(void)
+{
+ unsigned int core_pos = platform_get_core_pos(read_mpidr_el1());
+
+ write_icc_ppi_enabler0(cpu_ctx[core_pos].icc_ppi_enabler[0]);
+ write_icc_ppi_enabler1(cpu_ctx[core_pos].icc_ppi_enabler[1]);
+
+ write_icc_ppi_spendr0(cpu_ctx[core_pos].icc_ppi_xpendr[0]);
+ write_icc_ppi_spendr1(cpu_ctx[core_pos].icc_ppi_xpendr[1]);
+ /* clear interrupts that shouldn't be pending */
+ write_icc_ppi_cpendr0(~cpu_ctx[core_pos].icc_ppi_xpendr[0]);
+ write_icc_ppi_cpendr1(~cpu_ctx[core_pos].icc_ppi_xpendr[1]);
+
+ for (int i = 0; i < 15; i++) {
+ write_icc_ppi_priorityrn(i, cpu_ctx[core_pos].icc_ppi_priorityr[i]);
+ }
+
+ /* don't bother saving it, just put the same value in */
+ write_icc_pcr_el1(GICV5_IDLE_PRIORITY);
+
+ write_icc_cr0_el1(cpu_ctx[core_pos].icc_cr0_el1);
+ /* make sure effects are visible */
+ isb();
+}
+
+void gicv5_set_priority(unsigned int interrupt_id, unsigned int priority)
+{
+ unsigned irq_idx, irq_reg;
+ u_register_t priorityr;
+
+ assert(priority < (1UL << GICCDPRI_PRIORITY_WIDTH));
+ assert(is_interrupt(interrupt_id));
+
+ if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+ giccdpri(interrupt_id | INPLACE(GICCDPRI_PRIORITY, priority));
+ return;
+ }
+
+ /* it's a PPI, get rid of the INTR TYPE field */
+ interrupt_id = EXTRACT(INT_ID, interrupt_id);
+ irq_reg = interrupt_id / ICC_PPI_PRIORITYR_FIELD_NUM;
+ irq_idx = interrupt_id % ICC_PPI_PRIORITYR_FIELD_NUM;
+
+ priorityr = read_icc_ppi_priorityrn(irq_reg) &
+ ICC_PPI_PRIORITYR_FIELD_MASK <<
+ (irq_idx * ICC_PPI_PRIORITYR_FIELD_NUM);
+ write_icc_ppi_priorityrn(irq_reg, priorityr |
+ (priority << (irq_idx * ICC_PPI_PRIORITYR_FIELD_NUM)));
+}
+
+void gicv5_send_sgi(unsigned int sgi_id, unsigned int core_pos)
+{
+ giccdpend(gicv5_get_sgi_num(sgi_id, core_pos) | GICCDPEND_PENDING_BIT);
+}
+
+void gicv5_set_intr_route(unsigned int interrupt_id, unsigned int core_pos)
+{
+ assert(is_interrupt(interrupt_id));
+
+ /* PPIs are local to the CPU, can't be rerouted */
+ if (EXTRACT(INT_TYPE, interrupt_id) == INT_PPI) {
+ return;
+ }
+
+ /*
+ * The expecation is that a core will be up (CPU_ON) before it gets
+ * targetted by interrupts. Otherwise the IAFFID isn't available yet
+ * and the interrupt will be misrouted.
+ */
+ assert(iaffids[core_pos] != 0 || core_pos == 0);
+ giccdaff(INPLACE(GICCDAFF_IAFFID, iaffids[core_pos]) | interrupt_id);
+
+ /* wait for the target to take effect so retargetting an already
+ * enabled interrupt ends up in the correct destination */
+ gsbsys();
+}
+
+void gicv5_intr_enable(unsigned int interrupt_id)
+{
+ unsigned int irq_idx;
+
+ assert(is_interrupt(interrupt_id));
+
+ if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+ giccden(interrupt_id);
+ return;
+ }
+
+ /* it's a PPI, get rid of the INTR TYPE field */
+ interrupt_id = EXTRACT(INT_ID, interrupt_id);
+ irq_idx = interrupt_id % ICC_PPI_ENABLER_FIELD_NUM;
+
+ if (interrupt_id / ICC_PPI_ENABLER_FIELD_NUM == 0) {
+ write_icc_ppi_enabler0(read_icc_ppi_enabler0() | (1UL << irq_idx));
+ } else {
+ write_icc_ppi_enabler1(read_icc_ppi_enabler1() | (1UL << irq_idx));
+ }
+}
+
+void gicv5_intr_disable(unsigned int interrupt_id)
+{
+ unsigned int irq_idx;
+
+ assert(is_interrupt(interrupt_id));
+
+ if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+ giccddis(interrupt_id);
+ /* wait for the interrupt to become disabled */
+ gsbsys();
+ return;
+ }
+
+ /* it's a PPI, get rid of the INTR TYPE field */
+ interrupt_id = EXTRACT(INT_ID, interrupt_id);
+ irq_idx = interrupt_id % ICC_PPI_ENABLER_FIELD_NUM;
+
+ if (interrupt_id / ICC_PPI_ENABLER_FIELD_NUM == 0) {
+ write_icc_ppi_enabler0(read_icc_ppi_enabler0() & ~(1UL << irq_idx));
+ } else {
+ write_icc_ppi_enabler1(read_icc_ppi_enabler1() & ~(1UL << irq_idx));
+ }
+}
+
+unsigned int gicv5_acknowledge_interrupt(void)
+{
+ u_register_t iar = gicrcdia();
+ assert((iar & GICRCDIA_VALID_BIT) != 0);
+
+ /* wait for the intr ack to complete (i.e. make it Active) and refetch
+ * instructions so they don't operate on anything stale */
+ gsback();
+ isb();
+
+ return iar & ~GICRCDIA_VALID_BIT;
+}
+
+unsigned int gicv5_is_intr_pending(unsigned int interrupt_id)
+{
+ u_register_t icsr;
+ u_register_t ppi_spendr;
+
+ assert(is_interrupt(interrupt_id));
+
+ if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+ /* request interrupt information */
+ giccdrcfg(interrupt_id);
+ /* wait for the register to update */
+ isb();
+ icsr = read_icc_icsr_el1();
+
+ /* interrupt is unreachable, something has gone wrong */
+ assert((icsr & ICC_ICSR_EL1_F_BIT) == 0);
+ return !!(icsr & ICC_ICSR_EL1_PENDING_BIT);
+ }
+
+ /* it's a PPI, get rid of the INTR TYPE field */
+ interrupt_id = EXTRACT(INT_ID, interrupt_id);
+
+ if (interrupt_id / ICC_PPI_XPENDR_FIELD_NUM == 0) {
+ ppi_spendr = read_icc_ppi_spendr0();
+ } else {
+ ppi_spendr = read_icc_ppi_spendr1();
+ }
+
+ return !!(ppi_spendr & BIT(interrupt_id % ICC_PPI_XPENDR_FIELD_NUM));
+}
+
+void gicv5_intr_clear(unsigned int interrupt_id)
+{
+ unsigned int irq_idx;
+
+ assert(is_interrupt(interrupt_id));
+
+ if (EXTRACT(INT_TYPE, interrupt_id) != INT_PPI) {
+ giccdpend(interrupt_id);
+ return;
+ }
+
+ /* it's a PPI, get rid of the INTR TYPE field */
+ interrupt_id = EXTRACT(INT_ID, interrupt_id);
+ irq_idx = interrupt_id % ICC_PPI_XPENDR_FIELD_NUM;
+
+ if (interrupt_id / ICC_PPI_XPENDR_FIELD_NUM == 0) {
+ write_icc_ppi_cpendr0(read_icc_ppi_cpendr0() | (1UL << irq_idx));
+ } else {
+ write_icc_ppi_cpendr1(read_icc_ppi_cpendr1() | (1UL << irq_idx));
+ }
+}
+
+void gicv5_end_of_interrupt(unsigned int raw_iar)
+{
+ giccddi(raw_iar);
+ giccdeoi();
+ /* no isb as we won't interact with the gic before the eret */
+}
+
+/* currently a single IRS is expected and an ITS/IWB are not used */
+void gicv5_setup(void)
+{
+#if ENABLE_ASSERTIONS
+ uint32_t irs_idr2 = read_IRS_IDR2(irs_base);
+#endif
+ uint8_t id_bits = log2(ARRAY_SIZE(ist));
+ /* min_id_bits <= log2(length(ist)) <= id_bits */
+ assert(EXTRACT(IRS_IDR2_MIN_LPI_ID_BITS, irs_idr2) <= id_bits);
+ assert(EXTRACT(IRS_IDR2_ID_BITS, irs_idr2) >= id_bits);
+
+ /* make sure all ISTEs aren't enabled */
+ memset(ist, 0x0, sizeof(ist));
+
+ /* write zeroes throughout except LPI_ID_Bits which is the lowest 5 bits */
+ write_IRS_IST_CFGR(irs_base, id_bits);
+ /* make the IST valid */
+ write_IRS_IST_BASER(irs_base,
+ (((uintptr_t) &ist) & MASK(IRS_IST_BASER_ADDR)) |
+ IRS_IST_BASER_VALID_BIT);
+ WAIT_FOR_IDLE(irs_base, IRS_IST_STATUSR);
+
+ /* enable the IRS */
+ write_IRS_CR0(irs_base, IRS_CR0_IRSEN_BIT);
+ WAIT_FOR_IDLE(irs_base, IRS_CR0);
+}
+
+uint32_t gicv5_get_sgi_num(uint32_t index, unsigned int core_pos)
+{
+ assert(index <= IRQ_NUM_SGIS);
+
+ return (core_pos * IRQ_NUM_SGIS + index) | INPLACE(INT_TYPE, INT_LPI);
+}
+
+void gicv5_init(uintptr_t irs_base_addr)
+{
+ irs_base = irs_base_addr;
+}
diff --git a/drivers/arm/gic/arm_gic.c b/drivers/arm/gic/arm_gic.c
index d3b2536..5fe8316 100644
--- a/drivers/arm/gic/arm_gic.c
+++ b/drivers/arm/gic/arm_gic.c
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-3-Clause
*/
+#include <stdbool.h>
+
#include <arch.h>
#include <arch_helpers.h>
#include <assert.h>
@@ -11,15 +13,32 @@
#include <drivers/arm/gic_v2v3_common.h>
#include <drivers/arm/gic_v2.h>
#include <drivers/arm/gic_v3.h>
-#include <stdbool.h>
+#include <drivers/arm/gic_v5.h>
/* Record whether a GICv3 was detected on the system */
static unsigned int gicv3_detected;
+static unsigned int gicv5_detected;
+static bool gic_done_init;
+
+int arm_gic_get_version(void)
+{
+ assert(gic_done_init);
+
+ if (gicv3_detected) {
+ return 3;
+ } else if (gicv5_detected) {
+ return 5;
+ } else {
+ return 2;
+ }
+}
void arm_gic_enable_interrupts_local(void)
{
if (gicv3_detected)
gicv3_enable_cpuif();
+ else if (gicv5_detected)
+ gicv5_enable_cpuif();
else
gicv2_enable_cpuif();
}
@@ -29,6 +48,8 @@
if (gicv3_detected) {
gicv3_probe_redistif_addr();
gicv3_setup_cpuif();
+ } else if (gicv5_detected) {
+ gicv5_setup_cpuif();
} else {
gicv2_probe_gic_cpu_id();
gicv2_setup_cpuif();
@@ -39,6 +60,8 @@
{
if (gicv3_detected)
gicv3_disable_cpuif();
+ else if (gicv5_detected)
+ gicv5_disable_cpuif();
else
gicv2_disable_cpuif();
}
@@ -47,6 +70,8 @@
{
if (gicv3_detected)
gicv3_save_cpuif_context();
+ else if (gicv5_detected)
+ gicv5_save_cpuif_context();
else
gicv2_save_cpuif_context();
}
@@ -55,16 +80,21 @@
{
if (gicv3_detected)
gicv3_restore_cpuif_context();
+ else if (gicv5_detected)
+ gicv5_restore_cpuif_context();
else
gicv2_restore_cpuif_context();
}
void arm_gic_save_context_global(void)
{
- if (gicv3_detected)
+ if (gicv3_detected) {
gicv3_save_sgi_ppi_context();
- else
+ } else if (gicv5_detected) {
+ /* NOP, done by EL3 */
+ } else {
gicv2_save_sgi_ppi_context();
+ }
}
void arm_gic_restore_context_global(void)
@@ -72,6 +102,8 @@
if (gicv3_detected) {
gicv3_setup_distif();
gicv3_restore_sgi_ppi_context();
+ } else if (gicv5_detected) {
+ /* NOP, done by EL3 */
} else {
gicv2_setup_distif();
gicv2_restore_sgi_ppi_context();
@@ -82,6 +114,8 @@
{
if (gicv3_detected)
gicv3_setup_distif();
+ else if (gicv5_detected)
+ gicv5_setup();
else
gicv2_setup_distif();
}
@@ -90,7 +124,11 @@
{
if (gicv3_detected)
return gicv3_get_ipriorityr(num);
- else
+ /* TODO only used for SDEI, currently not supported/ported */
+ else if (gicv5_detected) {
+ assert(0);
+ return 0;
+ } else
return gicv2_gicd_get_ipriorityr(num);
}
@@ -99,6 +137,8 @@
{
if (gicv3_detected)
gicv3_set_ipriorityr(num, priority);
+ else if (gicv5_detected)
+ gicv5_set_priority(num, priority);
else
gicv2_gicd_set_ipriorityr(num, priority);
}
@@ -107,6 +147,8 @@
{
if (gicv3_detected)
gicv3_send_sgi(sgi_id, core_pos);
+ else if (gicv5_detected)
+ gicv5_send_sgi(sgi_id, core_pos);
else
gicv2_send_sgi(sgi_id, core_pos);
}
@@ -115,6 +157,8 @@
{
if (gicv3_detected)
gicv3_set_intr_route(num, core_pos);
+ else if (gicv5_detected)
+ gicv5_set_intr_route(num, core_pos);
else
gicv2_set_itargetsr(num, core_pos);
}
@@ -123,7 +167,11 @@
{
if (gicv3_detected)
return gicv3_get_isenabler(num) != 0;
- else
+ /* TODO only used for SDEI, currently not supported/ported */
+ else if (gicv5_detected) {
+ assert(0);
+ return 0;
+ } else
return gicv2_gicd_get_isenabler(num) != 0;
}
@@ -131,6 +179,8 @@
{
if (gicv3_detected)
gicv3_set_isenabler(num);
+ else if (gicv5_detected)
+ gicv5_intr_enable(num);
else
gicv2_gicd_set_isenabler(num);
}
@@ -139,6 +189,8 @@
{
if (gicv3_detected)
gicv3_set_icenabler(num);
+ else if (gicv5_detected)
+ gicv5_intr_disable(num);
else
gicv2_gicd_set_icenabler(num);
}
@@ -150,6 +202,9 @@
if (gicv3_detected) {
*raw_iar = gicv3_acknowledge_interrupt();
return *raw_iar;
+ } else if (gicv5_detected) {
+ *raw_iar = gicv5_acknowledge_interrupt();
+ return *raw_iar;
} else {
*raw_iar = gicv2_gicc_read_iar();
return get_gicc_iar_intid(*raw_iar);
@@ -160,6 +215,8 @@
{
if (gicv3_detected)
return gicv3_get_ispendr(num);
+ else if (gicv5_detected)
+ return gicv5_is_intr_pending(num);
else
return gicv2_gicd_get_ispendr(num);
}
@@ -168,6 +225,8 @@
{
if (gicv3_detected)
gicv3_set_icpendr(num);
+ else if (gicv5_detected)
+ gicv5_intr_clear(num);
else
gicv2_gicd_set_icpendr(num);
}
@@ -176,28 +235,44 @@
{
if (gicv3_detected)
gicv3_end_of_interrupt(raw_iar);
+ else if (gicv5_detected)
+ gicv5_end_of_interrupt(raw_iar);
else
gicv2_gicc_write_eoir(raw_iar);
}
+void arm_gic_probe(void)
+{
+ if (is_gicv3_mode()) {
+ gicv3_detected = 1;
+ INFO("GICv3 mode detected\n");
+ } else if (is_gicv5_mode()) {
+ gicv5_detected = 1;
+ INFO("GICv5 mode detected\n");
+ } else {
+ INFO("GICv2 mode detected\n");
+ }
+
+ gic_done_init = true;
+}
+
+/* to not change the API, pretend the IRS is a distributor */
void arm_gic_init(uintptr_t gicc_base,
uintptr_t gicd_base,
uintptr_t gicr_base)
{
-
- if (is_gicv3_mode()) {
- gicv3_detected = 1;
+ if (gicv3_detected) {
gicv3_init(gicr_base, gicd_base);
+ } else if (gicv5_detected) {
+ gicv5_init(gicd_base);
} else {
gicv2_init(gicc_base, gicd_base);
}
-
- INFO("%s mode detected\n", (gicv3_detected) ?
- "GICv3" : "GICv2");
}
bool arm_gic_is_espi_supported(void)
{
+ /* TODO only used for FFA, currently not supported/ported on GICv5 */
if (!gicv3_detected) {
return false;
}
diff --git a/include/drivers/arm/arm_gic.h b/include/drivers/arm/arm_gic.h
index 528ec6e..472f190 100644
--- a/include/drivers/arm/arm_gic.h
+++ b/include/drivers/arm/arm_gic.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -37,6 +37,9 @@
#define GIC_LOWEST_NS_PRIORITY 254 /* 255 would disable an interrupt */
#define GIC_SPURIOUS_INTERRUPT 1023
+/* return the GIC version detected */
+int arm_gic_get_version(void);
+
/******************************************************************************
* Setup the global GIC interface. In case of GICv2, it would be the GIC
* Distributor and in case of GICv3 it would be GIC Distributor and
@@ -121,6 +124,11 @@
void arm_gic_intr_clear(unsigned int num);
/******************************************************************************
+ * Discover the GIC version in use.
+ *****************************************************************************/
+void arm_gic_probe(void);
+
+/******************************************************************************
* Initialize the GIC Driver. This function will detect the GIC Architecture
* present on the system and initialize the appropriate driver. The
* `gicr_base` argument will be ignored on GICv2 systems.
diff --git a/include/drivers/arm/gic_v5.h b/include/drivers/arm/gic_v5.h
new file mode 100644
index 0000000..9c0b503
--- /dev/null
+++ b/include/drivers/arm/gic_v5.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2025, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#ifndef __GIC_V5_H__
+#define __GIC_V5_H__
+
+#include <drivers/arm/arm_gic.h>
+#include <lib/mmio.h>
+
+#define INT_PPI 1
+#define INT_LPI 2
+#define INT_SPI 3
+
+#define GICV5_MAX_PPI_ID 128
+
+#define GICV5_IDLE_PRIORITY 0xff
+
+#define INT_TYPE_SHIFT U(29)
+#define INT_TYPE_WIDTH U(3)
+#define INT_ID_SHIFT U(0)
+#define INT_ID_WIDTH U(24)
+
+#define IRM_TARGETTED 0
+#define IRM_1OFN 1
+
+#define HM_EDGE 0
+#define HM_LEVEL 1
+
+struct l2_iste {
+ uint16_t iaffid;
+ uint8_t priority : 5;
+ uint8_t hwu : 2;
+ uint8_t res0 : 4;
+ uint8_t irm : 1;
+ uint8_t enable : 1;
+ uint8_t hm : 1;
+ uint8_t active : 1;
+ uint8_t pending : 1;
+};
+
+#define DEFINE_GICV5_MMIO_WRITE_FUNC(_name, _offset) \
+static inline void write_##_name(uintptr_t base, uint32_t val) \
+{ \
+ mmio_write_32(base + _offset, val); \
+}
+
+#define DEFINE_GICV5_MMIO_READ_FUNC(_name, _offset) \
+static inline uint32_t read_##_name(uintptr_t base) \
+{ \
+ return mmio_read_32(base + _offset); \
+}
+
+#define DEFINE_GICV5_MMIO_RW_FUNCS(_name, _offset) \
+ DEFINE_GICV5_MMIO_READ_FUNC(_name, _offset) \
+ DEFINE_GICV5_MMIO_WRITE_FUNC(_name, _offset)
+
+#define IRS_IDR2_MIN_LPI_ID_BITS_WIDTH UL(4)
+#define IRS_IDR2_MIN_LPI_ID_BITS_SHIFT U(5)
+#define IRS_IDR2_ID_BITS_WIDTH UL(5)
+#define IRS_IDR2_ID_BITS_SHIFT U(0)
+#define IRS_CR0_IRSEN_BIT BIT(0)
+#define IRS_CR0_IDLE_BIT BIT(1)
+#define IRS_IST_STATUSR_IDLE_BIT BIT(0)
+#define IRS_IST_BASER_VALID_BIT BIT(0)
+#define IRS_IST_BASER_ADDR_SHIFT 6UL
+#define IRS_IST_BASER_ADDR_WIDTH 50UL
+
+DEFINE_GICV5_MMIO_READ_FUNC(IRS_IDR2, 0x0008)
+DEFINE_GICV5_MMIO_RW_FUNCS( IRS_CR0, 0x0080)
+DEFINE_GICV5_MMIO_RW_FUNCS( IRS_IST_BASER, 0x0180)
+DEFINE_GICV5_MMIO_RW_FUNCS( IRS_IST_CFGR, 0x0190)
+DEFINE_GICV5_MMIO_RW_FUNCS( IRS_IST_STATUSR, 0x0194)
+
+#define WAIT_FOR_IDLE(base, reg) \
+ do { \
+ while ((read_##reg(base) & reg##_IDLE_BIT) == 0) {} \
+ } while (0)
+
+#ifdef __aarch64__
+bool is_gicv5_mode(void);
+bool gicv5_is_irq_spi(unsigned int interrupt_id);
+void gicv5_enable_cpuif(void);
+void gicv5_setup_cpuif(void);
+void gicv5_disable_cpuif(void);
+void gicv5_save_cpuif_context(void);
+void gicv5_restore_cpuif_context(void);
+void gicv5_set_priority(unsigned int interrupt_id, unsigned int priority);
+void gicv5_send_sgi(unsigned int sgi_id, unsigned int core_pos);
+void gicv5_set_intr_route(unsigned int interrupt_id, unsigned int core_pos);
+void gicv5_intr_enable(unsigned int interrupt_id);
+void gicv5_intr_disable(unsigned int interrupt_id);
+unsigned int gicv5_acknowledge_interrupt(void);
+unsigned int gicv5_is_intr_pending(unsigned int interrupt_id);
+void gicv5_intr_clear(unsigned int interrupt_id);
+void gicv5_end_of_interrupt(unsigned int raw_iar);
+void gicv5_setup(void);
+uint32_t gicv5_get_sgi_num(uint32_t index, unsigned int core_pos);
+void gicv5_init(uintptr_t irs_base_addr);
+#else
+static inline bool is_gicv5_mode(void) { return false; }
+static inline bool gicv5_is_irq_spi(unsigned int interrupt_id) { return false; }
+static inline void gicv5_enable_cpuif(void) {}
+static inline void gicv5_setup_cpuif(void) {}
+static inline void gicv5_disable_cpuif(void) {}
+static inline void gicv5_save_cpuif_context(void) {}
+static inline void gicv5_restore_cpuif_context(void) {}
+static inline void gicv5_set_priority(unsigned int interrupt_id, unsigned int priority) {}
+static inline void gicv5_send_sgi(unsigned int sgi_id, unsigned int core_pos) {}
+static inline void gicv5_set_intr_route(unsigned int interrupt_id, unsigned int core_pos) {}
+static inline void gicv5_intr_enable(unsigned int interrupt_id) {}
+static inline void gicv5_intr_disable(unsigned int interrupt_id) {}
+static inline unsigned int gicv5_acknowledge_interrupt(void) { return -1; }
+static inline unsigned int gicv5_is_intr_pending(unsigned int interrupt_id) { return -1; }
+static inline void gicv5_intr_clear(unsigned int interrupt_id) {}
+static inline void gicv5_end_of_interrupt(unsigned int raw_iar) {}
+static inline void gicv5_setup(void) {}
+static inline uint32_t gicv5_get_sgi_num(uint32_t index, unsigned int core_pos) { return 0; }
+static inline void gicv5_init(uintptr_t irs_base_addr) {}
+#endif
+
+#endif /* __GIC_V5_H__ */
diff --git a/include/lib/aarch32/arch_features.h b/include/lib/aarch32/arch_features.h
index 44a6845..7c5aed7 100644
--- a/include/lib/aarch32/arch_features.h
+++ b/include/lib/aarch32/arch_features.h
@@ -71,4 +71,10 @@
return EXTRACT(ID_PFR1_GIC, read_id_pfr1()) >= 1;
}
+static inline bool is_feat_gcie_supported(void)
+{
+ /* v9 only, we can't have AArch32 in EL2 */
+ return 0;
+}
+
#endif /* ARCH_FEATURES_H */
diff --git a/include/lib/aarch64/arch.h b/include/lib/aarch64/arch.h
index 2258c7d..fbef12f 100644
--- a/include/lib/aarch64/arch.h
+++ b/include/lib/aarch64/arch.h
@@ -148,6 +148,20 @@
#define ICC_PPI_PRIORITYR14 S3_0_C12_C15_6
#define ICC_PPI_PRIORITYR15 S3_0_C12_C15_7
+#define ICC_CR0_EL1_EN_BIT BIT(0)
+#define ICC_ICSR_EL1_F_BIT BIT(0)
+#define ICC_ICSR_EL1_PENDING_BIT BIT(2)
+#define ICC_PPI_PRIORITYR_FIELD_NUM 8UL
+#define ICC_PPI_PRIORITYR_FIELD_MASK GENMASK(7, 0)
+#define ICC_PPI_ENABLER_FIELD_NUM 64UL
+#define ICC_PPI_XPENDR_FIELD_NUM 64UL
+
+#define GICCDAFF_IAFFID_SHIFT 32UL
+#define GICCDPRI_PRIORITY_SHIFT 35UL
+#define GICCDPRI_PRIORITY_WIDTH 5UL
+#define GICCDPEND_PENDING_BIT BIT(32)
+#define GICRCDIA_VALID_BIT BIT(32)
+
/*******************************************************************************
* Definitions for EL2 system registers.
******************************************************************************/
@@ -606,6 +620,11 @@
#define ID_AA64PFR2_EL1_FPMR_WIDTH U(4)
#define ID_AA64PFR2_EL1_FPMR_SUPPORTED ULL(0x1)
+#define ID_AA64PFR2_EL1_GCIE_SHIFT U(12)
+#define ID_AA64PFR2_EL1_GCIE_MASK ULL(0xf)
+#define ID_AA64PFR2_EL1_GCIE_WIDTH U(4)
+#define ID_AA64PFR2_EL1_GCIE_SUPPORTED ULL(0x1)
+
/* ID_PFR1_EL1 definitions */
#define ID_PFR1_VIRTEXT_SHIFT U(12)
#define ID_PFR1_VIRTEXT_MASK U(0xf)
diff --git a/include/lib/aarch64/arch_features.h b/include/lib/aarch64/arch_features.h
index eb846cd..a873501 100644
--- a/include/lib/aarch64/arch_features.h
+++ b/include/lib/aarch64/arch_features.h
@@ -619,4 +619,10 @@
return EXTRACT(ID_AA64PFR0_GIC, read_id_aa64pfr0_el1())
>= ID_AA64PFR0_GICV3_GICV4_SUPPORTED;
}
+
+static inline bool is_feat_gcie_supported(void)
+{
+ return EXTRACT(ID_AA64PFR2_EL1_GCIE, read_id_aa64pfr2_el1())
+ >= ID_AA64PFR2_EL1_GCIE_SUPPORTED;
+}
#endif /* ARCH_FEATURES_H */
diff --git a/include/lib/irq.h b/include/lib/irq.h
index 17993a8..d4d0a3c 100644
--- a/include/lib/irq.h
+++ b/include/lib/irq.h
@@ -16,6 +16,7 @@
* timer fires off
*/
#define IRQ_WAKE_SGI IRQ_NS_SGI_7
+#define IRQ_NUM_SGIS (IRQ_NS_SGI_7 + 1)
#ifndef __ASSEMBLY__
diff --git a/include/lib/utils_def.h b/include/lib/utils_def.h
index c9bd527..ae85a81 100644
--- a/include/lib/utils_def.h
+++ b/include/lib/utils_def.h
@@ -233,4 +233,14 @@
0; \
}))
+/*
+ * Get the next power of 2 for x. Eg. next_power_of_2(12) is 16
+ */
+#define b2(x) ((x) | ((x) >> 1))
+#define b4(x) (b2(x) | (b2(x) >> 2))
+#define b8(x) (b4(x) | (b4(x) >> 4))
+#define b16(x) (b8(x) | (b8(x) >> 8))
+#define b32(x) (b16(x) | (b16(x) >> 16))
+#define next_power_of_2(x) (b32(x - 1) + 1)
+
#endif /* UTILS_DEF_H */
diff --git a/plat/arm/fvp/fvp_def.h b/plat/arm/fvp/fvp_def.h
index 222fadd..1d4e507 100644
--- a/plat/arm/fvp/fvp_def.h
+++ b/plat/arm/fvp/fvp_def.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2024, Arm Limited. All rights reserved.
+ * Copyright (c) 2018-2025, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
@@ -71,6 +71,11 @@
#define GICC_BASE 0x2c000000
/*******************************************************************************
+ * GICv5 related constants
+ ******************************************************************************/
+#define IRS_BASE 0x2f1a0000
+
+/*******************************************************************************
* PL011 related constants
******************************************************************************/
#define PL011_UART0_BASE 0x1c090000
diff --git a/plat/arm/fvp/plat_setup.c b/plat/arm/fvp/plat_setup.c
index f09bf59..c897289 100644
--- a/plat/arm/fvp/plat_setup.c
+++ b/plat/arm/fvp/plat_setup.c
@@ -52,5 +52,9 @@
void plat_arm_gic_init(void)
{
- arm_gic_init(GICC_BASE, GICD_BASE, GICR_BASE);
+ if (arm_gic_get_version() != 5) {
+ arm_gic_init(GICC_BASE, GICD_BASE, GICR_BASE);
+ } else {
+ arm_gic_init(0, IRS_BASE, 0);
+ }
}
diff --git a/tftf/framework/framework.mk b/tftf/framework/framework.mk
index edcae60..f920cbf 100644
--- a/tftf/framework/framework.mk
+++ b/tftf/framework/framework.mk
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2018-2024, Arm Limited. All rights reserved.
+# Copyright (c) 2018-2025, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
@@ -90,6 +90,7 @@
ifeq (${ARCH},aarch64)
# ARMv8.3 Pointer Authentication support files
FRAMEWORK_SOURCES += \
+ drivers/arm/gic/aarch64/gic_v5.c \
lib/exceptions/aarch64/sync.c \
lib/exceptions/aarch64/serror.c \
lib/extensions/pauth/aarch64/pauth.c \
diff --git a/tftf/framework/main.c b/tftf/framework/main.c
index 0ae03e5..51e49b8 100644
--- a/tftf/framework/main.c
+++ b/tftf/framework/main.c
@@ -564,6 +564,8 @@
pauth_init_enable();
#endif /* ENABLE_PAUTH */
+ arm_gic_probe();
+
tftf_platform_setup();
tftf_init_topology();