GIC: Add APIs to set interrupt type and query support

The back end GIC driver converts and assigns the interrupt type to
suitable group.

For GICv2, a build option GICV2_G0_FOR_EL3 is introduced, which
determines to which type Group 0 interrupts maps to.

 - When the build option is set 0 (the default), Group 0 interrupts are
   meant for Secure EL1. This is presently the case.

 - Otherwise, Group 0 interrupts are meant for EL3. This means the SPD
   will have to synchronously hand over the interrupt to Secure EL1.

The query API allows the platform to query whether the platform supports
interrupts of a given type.

API documentation updated.

Change-Id: I60fdb4053ffe0bd006b3b20914914ebd311fc858
Co-authored-by: Yousuf A <yousuf.sait@arm.com>
Signed-off-by: Jeenu Viswambharan <jeenu.viswambharan@arm.com>
diff --git a/Makefile b/Makefile
index a7d3a87..b32b417 100644
--- a/Makefile
+++ b/Makefile
@@ -449,6 +449,7 @@
 $(eval $(call assert_boolean,ENABLE_SPE_FOR_LOWER_ELS))
 $(eval $(call assert_boolean,ERROR_DEPRECATED))
 $(eval $(call assert_boolean,GENERATE_COT))
+$(eval $(call assert_boolean,GICV2_G0_FOR_EL3))
 $(eval $(call assert_boolean,HW_ASSISTED_COHERENCY))
 $(eval $(call assert_boolean,LOAD_IMAGE_V2))
 $(eval $(call assert_boolean,NS_TIMER_SWITCH))
@@ -486,6 +487,7 @@
 $(eval $(call add_define,ENABLE_RUNTIME_INSTRUMENTATION))
 $(eval $(call add_define,ENABLE_SPE_FOR_LOWER_ELS))
 $(eval $(call add_define,ERROR_DEPRECATED))
+$(eval $(call add_define,GICV2_G0_FOR_EL3))
 $(eval $(call add_define,HW_ASSISTED_COHERENCY))
 $(eval $(call add_define,LOAD_IMAGE_V2))
 $(eval $(call add_define,LOG_LEVEL))
diff --git a/docs/platform-interrupt-controller-API.rst b/docs/platform-interrupt-controller-API.rst
index 3161c20..3d46cf3 100644
--- a/docs/platform-interrupt-controller-API.rst
+++ b/docs/platform-interrupt-controller-API.rst
@@ -126,6 +126,80 @@
 In case of ARM standard platforms using GIC, the implementation of the API
 writes to GIC *Priority Register* set interrupt priority.
 
+Function: int plat_ic_has_interrupt_type(unsigned int type); [optional]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+    Argument : unsigned int
+    Return   : int
+
+This API should return whether the platform supports a given interrupt type. The
+parameter ``type`` shall be one of ``INTR_TYPE_EL3``, ``INTR_TYPE_S_EL1``, or
+``INTR_TYPE_NS``.
+
+In case of ARM standard platforms using GICv3, the implementation of the API
+returns ``1`` for all interrupt types.
+
+In case of ARM standard platforms using GICv2, the API always return ``1`` for
+``INTR_TYPE_NS``. Return value for other types depends on the value of build
+option ``GICV2_G0_FOR_EL3``:
+
+- For interrupt type ``INTR_TYPE_EL3``:
+
+  - When ``GICV2_G0_FOR_EL3`` is ``0``, it returns ``0``, indicating no support
+    for EL3 interrupts.
+
+  - When ``GICV2_G0_FOR_EL3`` is ``1``, it returns ``1``, indicating support for
+    EL3 interrupts.
+
+- For interrupt type ``INTR_TYPE_S_EL1``:
+
+  - When ``GICV2_G0_FOR_EL3`` is ``0``, it returns ``1``, indicating support for
+    Secure EL1 interrupts.
+
+  - When ``GICV2_G0_FOR_EL3`` is ``1``, it returns ``0``, indicating no support
+    for Secure EL1 interrupts.
+
+Function: void plat_ic_set_interrupt_type(unsigned int id, unsigned int type); [optional]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+    Argument : unsigned int
+    Argument : unsigned int
+    Return   : void
+
+This API should set the interrupt specified by first parameter ``id`` to the
+type specified by second parameter ``type``. The ``type`` parameter can be
+one of:
+
+- ``INTR_TYPE_NS``: interrupt is meant to be consumed by the Non-secure world.
+
+- ``INTR_TYPE_S_EL1``: interrupt is meant to be consumed by Secure EL1.
+
+- ``INTR_TYPE_EL3``: interrupt is meant to be consumed by EL3.
+
+In case of ARM standard platforms using GIC, the implementation of the API
+writes to the GIC *Group Register* and *Group Modifier Register* (only GICv3) to
+assign the interrupt to the right group.
+
+For GICv3:
+
+- ``INTR_TYPE_NS`` maps to Group 1 interrupt.
+
+- ``INTR_TYPE_S_EL1`` maps to Secure Group 1 interrupt.
+
+- ``INTR_TYPE_EL3`` maps to Secure Group 0 interrupt.
+
+For GICv2:
+
+- ``INTR_TYPE_NS`` maps to Group 1 interrupt.
+
+- When the build option ``GICV2_G0_FOR_EL3`` is set to ``0`` (the default),
+  ``INTR_TYPE_S_EL1`` maps to Group 0. Otherwise, ``INTR_TYPE_EL3`` maps to
+  Group 0 interrupt.
+
 ----
 
 *Copyright (c) 2017, ARM Limited and Contributors. All rights reserved.*
diff --git a/docs/user-guide.rst b/docs/user-guide.rst
index 67af425..80273be 100644
--- a/docs/user-guide.rst
+++ b/docs/user-guide.rst
@@ -386,6 +386,19 @@
    images will include support for Trusted Board Boot, but the FIP and FWU\_FIP
    will not include the corresponding certificates, causing a boot failure.
 
+-  ``GICV2_G0_FOR_EL3``: Unlike GICv3, the GICv2 architecture doesn't have
+   inherent support for specific EL3 type interrupts. Setting this build option
+   to ``1`` assumes GICv2 *Group 0* interrupts are expected to target EL3, both
+   by `platform abstraction layer`__ and `Interrupt Management Framework`__.
+   This allows GICv2 platforms to enable features requiring EL3 interrupt type.
+   This also means that all GICv2 Group 0 interrupts are delivered to EL3, and
+   the Secure Payload interrupts needs to be synchronously handed over to Secure
+   EL1 for handling. The default value of this option is ``0``, which means the
+   Group 0 interrupts are assumed to be handled by Secure EL1.
+
+   .. __: `platform-interrupt-controller-API.rst`
+   .. __: `interrupt-framework-design.rst`
+
 -  ``HANDLE_EA_EL3_FIRST``: When defined External Aborts and SError Interrupts
    will be always trapped in EL3 i.e. in BL31 at runtime.
 
diff --git a/drivers/arm/gic/v2/gicv2_main.c b/drivers/arm/gic/v2/gicv2_main.c
index 4861e02..8048c6a 100644
--- a/drivers/arm/gic/v2/gicv2_main.c
+++ b/drivers/arm/gic/v2/gicv2_main.c
@@ -10,11 +10,19 @@
 #include <debug.h>
 #include <gic_common.h>
 #include <gicv2.h>
+#include <spinlock.h>
 #include "../common/gic_common_private.h"
 #include "gicv2_private.h"
 
 static const gicv2_driver_data_t *driver_data;
 
+/*
+ * Spinlock to guard registers needing read-modify-write. APIs protected by this
+ * spinlock are used either at boot time (when only a single CPU is active), or
+ * when the system is fully coherent.
+ */
+spinlock_t gic_lock;
+
 /*******************************************************************************
  * Enable secure interrupts and use FIQs to route them. Disable legacy bypass
  * and set the priority mask register to allow all interrupts to trickle in.
@@ -335,3 +343,28 @@
 
 	gicd_set_ipriorityr(driver_data->gicd_base, id, priority);
 }
+
+/*******************************************************************************
+ * This function assigns group for the interrupt identified by id. The group can
+ * be any of GICV2_INTR_GROUP*
+ ******************************************************************************/
+void gicv2_set_interrupt_type(unsigned int id, unsigned int type)
+{
+	assert(driver_data);
+	assert(driver_data->gicd_base);
+	assert(id <= MAX_SPI_ID);
+
+	/* Serialize read-modify-write to Distributor registers */
+	spin_lock(&gic_lock);
+	switch (type) {
+	case GICV2_INTR_GROUP1:
+		gicd_set_igroupr(driver_data->gicd_base, id);
+		break;
+	case GICV2_INTR_GROUP0:
+		gicd_clr_igroupr(driver_data->gicd_base, id);
+		break;
+	default:
+		assert(0);
+	}
+	spin_unlock(&gic_lock);
+}
diff --git a/drivers/arm/gic/v3/gicv3_main.c b/drivers/arm/gic/v3/gicv3_main.c
index d0ecab6..04b4712 100644
--- a/drivers/arm/gic/v3/gicv3_main.c
+++ b/drivers/arm/gic/v3/gicv3_main.c
@@ -9,12 +9,20 @@
 #include <assert.h>
 #include <debug.h>
 #include <gicv3.h>
+#include <spinlock.h>
 #include "gicv3_private.h"
 
 const gicv3_driver_data_t *gicv3_driver_data;
 static unsigned int gicv2_compat;
 
 /*
+ * Spinlock to guard registers needing read-modify-write. APIs protected by this
+ * spinlock are used either at boot time (when only a single CPU is active), or
+ * when the system is fully coherent.
+ */
+spinlock_t gic_lock;
+
+/*
  * Redistributor power operations are weakly bound so that they can be
  * overridden
  */
@@ -892,3 +900,63 @@
 		gicd_set_ipriorityr(gicv3_driver_data->gicd_base, id, priority);
 	}
 }
+
+/*******************************************************************************
+ * This function assigns group for the interrupt identified by id. The proc_num
+ * is used if the interrupt is SGI or PPI, and programs the corresponding
+ * Redistributor interface. The group can be any of GICV3_INTR_GROUP*
+ ******************************************************************************/
+void gicv3_set_interrupt_type(unsigned int id, unsigned int proc_num,
+		unsigned int type)
+{
+	unsigned int igroup = 0, grpmod = 0;
+	uintptr_t gicr_base;
+
+	assert(gicv3_driver_data);
+	assert(gicv3_driver_data->gicd_base);
+	assert(proc_num < gicv3_driver_data->rdistif_num);
+	assert(gicv3_driver_data->rdistif_base_addrs);
+
+	switch (type) {
+	case INTR_GROUP1S:
+		igroup = 0;
+		grpmod = 1;
+		break;
+	case INTR_GROUP0:
+		igroup = 0;
+		grpmod = 0;
+		break;
+	case INTR_GROUP1NS:
+		igroup = 1;
+		grpmod = 0;
+		break;
+	default:
+		assert(0);
+	}
+
+	if (id < MIN_SPI_ID) {
+		gicr_base = gicv3_driver_data->rdistif_base_addrs[proc_num];
+		if (igroup)
+			gicr_set_igroupr0(gicr_base, id);
+		else
+			gicr_clr_igroupr0(gicr_base, id);
+
+		if (grpmod)
+			gicr_set_igrpmodr0(gicr_base, id);
+		else
+			gicr_clr_igrpmodr0(gicr_base, id);
+	} else {
+		/* Serialize read-modify-write to Distributor registers */
+		spin_lock(&gic_lock);
+		if (igroup)
+			gicd_set_igroupr(gicv3_driver_data->gicd_base, id);
+		else
+			gicd_clr_igroupr(gicv3_driver_data->gicd_base, id);
+
+		if (grpmod)
+			gicd_set_igrpmodr(gicv3_driver_data->gicd_base, id);
+		else
+			gicd_clr_igrpmodr(gicv3_driver_data->gicd_base, id);
+		spin_unlock(&gic_lock);
+	}
+}
diff --git a/include/drivers/arm/gicv2.h b/include/drivers/arm/gicv2.h
index 91674fd..229355c 100644
--- a/include/drivers/arm/gicv2.h
+++ b/include/drivers/arm/gicv2.h
@@ -10,6 +10,11 @@
 /*******************************************************************************
  * GICv2 miscellaneous definitions
  ******************************************************************************/
+
+/* Interrupt group definitions */
+#define GICV2_INTR_GROUP0	0
+#define GICV2_INTR_GROUP1	1
+
 /* Interrupt IDs reported by the HPPIR and IAR registers */
 #define PENDING_G1_INTID	1022
 
@@ -151,6 +156,7 @@
 void gicv2_enable_interrupt(unsigned int id);
 void gicv2_disable_interrupt(unsigned int id);
 void gicv2_set_interrupt_priority(unsigned int id, unsigned int priority);
+void gicv2_set_interrupt_type(unsigned int id, unsigned int type);
 
 #endif /* __ASSEMBLY__ */
 #endif /* __GICV2_H__ */
diff --git a/include/drivers/arm/gicv3.h b/include/drivers/arm/gicv3.h
index f753ca2..68430fb 100644
--- a/include/drivers/arm/gicv3.h
+++ b/include/drivers/arm/gicv3.h
@@ -355,6 +355,8 @@
 void gicv3_disable_interrupt(unsigned int id, unsigned int proc_num);
 void gicv3_set_interrupt_priority(unsigned int id, unsigned int proc_num,
 		unsigned int priority);
+void gicv3_set_interrupt_type(unsigned int id, unsigned int proc_num,
+		unsigned int group);
 
 #endif /* __ASSEMBLY__ */
 #endif /* __GICV3_H__ */
diff --git a/include/plat/common/platform.h b/include/plat/common/platform.h
index 03f529c..c97b7d3 100644
--- a/include/plat/common/platform.h
+++ b/include/plat/common/platform.h
@@ -79,6 +79,8 @@
 unsigned int plat_ic_get_interrupt_active(unsigned int id);
 void plat_ic_disable_interrupt(unsigned int id);
 void plat_ic_enable_interrupt(unsigned int id);
+int plat_ic_has_interrupt_type(unsigned int type);
+void plat_ic_set_interrupt_type(unsigned int id, unsigned int type);
 void plat_ic_set_interrupt_priority(unsigned int id, unsigned int priority);
 
 /*******************************************************************************
diff --git a/make_helpers/defaults.mk b/make_helpers/defaults.mk
index 8601046..412c3b7 100644
--- a/make_helpers/defaults.mk
+++ b/make_helpers/defaults.mk
@@ -77,6 +77,10 @@
 # For Chain of Trust
 GENERATE_COT			:= 0
 
+# Hint platform interrupt control layer that Group 0 interrupts are for EL3. By
+# default, they are for Secure EL1.
+GICV2_G0_FOR_EL3		:= 0
+
 # Whether system coherency is managed in hardware, without explicit software
 # operations.
 HW_ASSISTED_COHERENCY		:= 0
diff --git a/plat/common/plat_gicv2.c b/plat/common/plat_gicv2.c
index d50138e..c785d83 100644
--- a/plat/common/plat_gicv2.c
+++ b/plat/common/plat_gicv2.c
@@ -28,6 +28,7 @@
 #pragma weak plat_ic_enable_interrupt
 #pragma weak plat_ic_disable_interrupt
 #pragma weak plat_ic_set_interrupt_priority
+#pragma weak plat_ic_set_interrupt_type
 
 /*
  * This function returns the highest priority pending interrupt at
@@ -62,8 +63,13 @@
 	id = gicv2_get_pending_interrupt_type();
 
 	/* Assume that all secure interrupts are S-EL1 interrupts */
-	if (id < PENDING_G1_INTID)
+	if (id < PENDING_G1_INTID) {
+#if GICV2_G0_FOR_EL3
+		return INTR_TYPE_EL3;
+#else
 		return INTR_TYPE_S_EL1;
+#endif
+	}
 
 	if (id == GIC_SPURIOUS_INTERRUPT)
 		return INTR_TYPE_INVAL;
@@ -92,7 +98,12 @@
 	type = gicv2_get_interrupt_group(id);
 
 	/* Assume that all secure interrupts are S-EL1 interrupts */
-	return (type) ? INTR_TYPE_NS : INTR_TYPE_S_EL1;
+	return type == GICV2_INTR_GROUP1 ? INTR_TYPE_NS :
+#if GICV2_G0_FOR_EL3
+		INTR_TYPE_EL3;
+#else
+		INTR_TYPE_S_EL1;
+#endif
 }
 
 /*
@@ -171,3 +182,41 @@
 {
 	gicv2_set_interrupt_priority(id, priority);
 }
+
+int plat_ic_has_interrupt_type(unsigned int type)
+{
+	switch (type) {
+#if GICV2_G0_FOR_EL3
+	case INTR_TYPE_EL3:
+#else
+	case INTR_TYPE_S_EL1:
+#endif
+	case INTR_TYPE_NS:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+void plat_ic_set_interrupt_type(unsigned int id, unsigned int type)
+{
+	int gicv2_type = 0;
+
+	/* Map canonical interrupt type to GICv2 type */
+	switch (type) {
+#if GICV2_G0_FOR_EL3
+	case INTR_TYPE_EL3:
+#else
+	case INTR_TYPE_S_EL1:
+#endif
+		gicv2_type = GICV2_INTR_GROUP0;
+		break;
+	case INTR_TYPE_NS:
+		gicv2_type = GICV2_INTR_GROUP1;
+		break;
+	default:
+		assert(0);
+	}
+
+	gicv2_set_interrupt_type(id, gicv2_type);
+}
diff --git a/plat/common/plat_gicv3.c b/plat/common/plat_gicv3.c
index 230d90c..f4279ec 100644
--- a/plat/common/plat_gicv3.c
+++ b/plat/common/plat_gicv3.c
@@ -34,6 +34,7 @@
 #pragma weak plat_ic_enable_interrupt
 #pragma weak plat_ic_disable_interrupt
 #pragma weak plat_ic_set_interrupt_priority
+#pragma weak plat_ic_set_interrupt_type
 
 CASSERT((INTR_TYPE_S_EL1 == INTR_GROUP1S) &&
 	(INTR_TYPE_NS == INTR_GROUP1NS) &&
@@ -204,6 +205,18 @@
 {
 	gicv3_set_interrupt_priority(id, plat_my_core_pos(), priority);
 }
+
+int plat_ic_has_interrupt_type(unsigned int type)
+{
+	assert((type == INTR_TYPE_EL3) || (type == INTR_TYPE_S_EL1) ||
+			(type == INTR_TYPE_NS));
+	return 1;
+}
+
+void plat_ic_set_interrupt_type(unsigned int id, unsigned int type)
+{
+	gicv3_set_interrupt_type(id, plat_my_core_pos(), type);
+}
 #endif
 #ifdef IMAGE_BL32