feat: route secure interrupts to any physical PE

This patch makes changes necessary to allow a physical interrupt to be
routed to a particular PE (by mpidr). Until now, all interrupts were
routed to the boot core by default. To maintain compatiblity with
existing behavior and manifests, the patch adds a new manifest field
'interrupts-target' through which the interrupt id and corresponding
MPIDR are supplied. This is then used to configure the gic
appropriately, to route interrupts to the desired PE.

Change-Id: I3ed8f51e7679bb65c0aca030b21395598553e6ec
Signed-off-by: Raghu Krishnamurthy <raghu.ncstate@gmail.com>
diff --git a/inc/hf/interrupt_desc.h b/inc/hf/interrupt_desc.h
index 958777c..6fcb938 100644
--- a/inc/hf/interrupt_desc.h
+++ b/inc/hf/interrupt_desc.h
@@ -102,6 +102,8 @@
 	uint8_t type_config_sec_state;
 	uint8_t priority;
 	bool valid;
+	bool mpidr_valid;
+	uint64_t mpidr;
 };
 
 /**
@@ -137,6 +139,18 @@
 	return int_desc.priority;
 }
 
+static inline uint64_t interrupt_desc_get_mpidr(
+	struct interrupt_descriptor int_desc)
+{
+	return int_desc.mpidr;
+}
+
+static inline bool interrupt_desc_get_mpidr_valid(
+	struct interrupt_descriptor int_desc)
+{
+	return int_desc.mpidr_valid;
+}
+
 static inline bool interrupt_desc_get_valid(
 	struct interrupt_descriptor int_desc)
 {
@@ -149,6 +163,20 @@
 	int_desc->interrupt_id = interrupt_id;
 }
 
+static inline void interrupt_desc_set_mpidr(
+	struct interrupt_descriptor *int_desc, uint64_t mpidr)
+{
+	int_desc->mpidr_valid = true;
+	int_desc->mpidr = mpidr;
+}
+
+static inline void interrupt_desc_set_mpidr_invalid(
+	struct interrupt_descriptor *int_desc)
+{
+	int_desc->mpidr_valid = false;
+	int_desc->mpidr = 0;
+}
+
 static inline void interrupt_desc_set_type_config_sec_state(
 	struct interrupt_descriptor *int_desc, uint8_t value)
 {
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index d2f9428..3f4cd3e 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -67,6 +67,8 @@
 struct interrupt_info {
 	uint32_t id;
 	uint32_t attributes;
+	bool mpidr_valid;
+	uint64_t mpidr;
 };
 
 /**
@@ -231,6 +233,7 @@
 	MANIFEST_ERROR_INVALID_MEM_PERM,
 	MANIFEST_ERROR_INTERRUPT_ID_REPEATED,
 	MANIFEST_ILLEGAL_NS_ACTION,
+	MANIFEST_ERROR_INTERRUPT_ID_NOT_IN_LIST,
 };
 
 enum manifest_return_code manifest_init(struct mm_stage1_locked stage1_locked,
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 0159d01..3bd272c 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -1908,7 +1908,7 @@
 
 void plat_ffa_sri_init(struct cpu *cpu)
 {
-	struct interrupt_descriptor sri_desc;
+	struct interrupt_descriptor sri_desc = {0};
 
 	/* TODO: when supported, make the interrupt driver use cpu structure. */
 	(void)cpu;
diff --git a/src/arch/aarch64/plat/interrupts/gicv3.c b/src/arch/aarch64/plat/interrupts/gicv3.c
index 3e8bde8..7e7c7ea 100644
--- a/src/arch/aarch64/plat/interrupts/gicv3.c
+++ b/src/arch/aarch64/plat/interrupts/gicv3.c
@@ -657,12 +657,22 @@
 		gicd_set_icfgr(plat_gicv3_driver.dist_base, intr_num, config);
 	}
 
-	/* Target SPI to primary CPU using affinity routing. */
+	/*
+	 * Target SPI to primary PE using affinity routing if no PE was
+	 * specified in the manifest. If one was specified, target the interrupt
+	 * to the corresponding PE.
+	 */
 	if (IS_SPI(intr_num)) {
 		uint64_t gic_affinity_val;
 
-		gic_affinity_val =
-			gicd_irouter_val_from_mpidr(read_msr(MPIDR_EL1), 0U);
+		if (interrupt_desc_get_mpidr_valid(int_desc)) {
+			gic_affinity_val = gicd_irouter_val_from_mpidr(
+				interrupt_desc_get_mpidr(int_desc),
+				GICV3_IRM_PE);
+		} else {
+			gic_affinity_val = gicd_irouter_val_from_mpidr(
+				read_msr(MPIDR_EL1), 0U);
+		}
 		gicd_write_irouter(plat_gicv3_driver.dist_base, intr_num,
 				   gic_affinity_val);
 	}
diff --git a/src/load.c b/src/load.c
index af3519a..0e277e6 100644
--- a/src/load.c
+++ b/src/load.c
@@ -143,6 +143,12 @@
 			(((attr >> INT_DESC_CONFIG_SHIFT) & 0x1) << 1) |
 			((attr >> INT_DESC_SEC_STATE_SHIFT) & 0x1));
 
+	if (interrupt.mpidr_valid) {
+		interrupt_desc_set_mpidr(int_desc, interrupt.mpidr);
+	} else {
+		interrupt_desc_set_mpidr_invalid(int_desc);
+	}
+
 	interrupt_desc_set_valid(int_desc, true);
 }
 
@@ -170,7 +176,7 @@
 		      PARTITION_MAX_INTERRUPTS_PER_DEVICE);
 
 		for (uint8_t j = 0; j < dev_region.interrupt_count; j++) {
-			struct interrupt_descriptor int_desc;
+			struct interrupt_descriptor int_desc = {0};
 
 			interrupt = dev_region.interrupts[j];
 			infer_interrupt(interrupt, &int_desc);
diff --git a/src/manifest.c b/src/manifest.c
index 8570148..2eeaf98 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -538,6 +538,17 @@
 	return MANIFEST_SUCCESS;
 }
 
+static struct interrupt_info *device_region_get_interrupt_info(
+	struct device_region *dev_regions, uint32_t intid)
+{
+	for (uint32_t i = 0; i < ARRAY_SIZE(dev_regions->interrupts); i++) {
+		if (dev_regions->interrupts[i].id == intid) {
+			return &(dev_regions->interrupts[i]);
+		}
+	}
+	return NULL;
+}
+
 static enum manifest_return_code parse_ffa_device_region_node(
 	struct fdt_node *dev_node, struct device_region *dev_regions,
 	uint16_t *count)
@@ -629,6 +640,9 @@
 				return MANIFEST_ERROR_MALFORMED_INTEGER_LIST;
 			}
 
+			dev_regions[i].interrupts[j].mpidr_valid = false;
+			dev_regions[i].interrupts[j].mpidr = 0;
+
 			dlog_verbose("        attributes = %u\n",
 				     dev_regions[i].interrupts[j].attributes);
 			j++;
@@ -637,6 +651,46 @@
 		dev_regions[i].interrupt_count = j;
 		if (j == 0) {
 			dlog_verbose("        Empty\n");
+		} else {
+			TRY(read_optional_uint32list(
+				dev_node, "interrupts-target", &list));
+			dlog_verbose("      Interrupt Target List:\n");
+
+			while (uint32list_has_next(&list)) {
+				uint32_t intid;
+				uint64_t mpidr = 0;
+				uint32_t mpidr_lower = 0;
+				uint32_t mpidr_upper = 0;
+				struct interrupt_info *info = NULL;
+
+				TRY(uint32list_get_next(&list, &intid));
+
+				dlog_verbose("        ID = %u\n", intid);
+
+				if (interrupt_bitmap_get_value(
+					    &allocated_intids, intid) != 1U) {
+					return MANIFEST_ERROR_INTERRUPT_ID_NOT_IN_LIST;
+				}
+
+				TRY(uint32list_get_next(&list, &mpidr_upper));
+				TRY(uint32list_get_next(&list, &mpidr_lower));
+				mpidr = mpidr_upper;
+				mpidr <<= 32;
+				mpidr |= mpidr_lower;
+
+				info = device_region_get_interrupt_info(
+					&dev_regions[i], intid);
+				/*
+				 * We should find info since
+				 * interrupt_bitmap_get_value already ensures
+				 * that we saw the interrupt and allocated ids
+				 * for it.
+				 */
+				assert(info != NULL);
+				info->mpidr = mpidr;
+				info->mpidr_valid = true;
+				dlog_verbose("        MPIDR = %#x\n", mpidr);
+			}
 		}
 
 		TRY(read_optional_uint32(dev_node, "smmu-id",
@@ -1191,6 +1245,8 @@
 	case MANIFEST_ILLEGAL_NS_ACTION:
 		return "Illegal value specidied for the field: Action in "
 		       "response to NS Interrupt";
+	case MANIFEST_ERROR_INTERRUPT_ID_NOT_IN_LIST:
+		return "Interrupt ID is not in the list of interrupts";
 	}
 
 	panic("Unexpected manifest return code.");
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index 77e69ff..b3807ea 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -1389,4 +1389,77 @@
 	ASSERT_EQ(vm->partition.dev_regions[1].attributes, (8 | 1));
 }
 
+TEST_F(manifest, ffa_valid_interrupt_target_manifest)
+{
+	struct manifest_vm *vm;
+	struct_manifest *m;
+
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.FfaValidManifest()
+		.StartChild("device-regions")
+			.Compatible({ "arm,ffa-manifest-device-regions" })
+			.StartChild("test-device")
+				.Description("test-device")
+				.Property("base-address", "<0x7400000>")
+				.Property("pages-count", "<16>")
+				.Property("attributes", "<3>")
+				.Property("smmu-id", "<1>")
+				.Property("stream-ids", "<0 1>")
+				.Property("interrupts", "<2 3>, <4 5>")
+				.Property("interrupts-target", "<2 0x1234 0x5678>, <4 0x12345678 0x87654321>")
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
+
+	vm = &m->vm[0];
+
+	ASSERT_EQ(vm->partition.dev_regions[0].base_address, 0x7400000);
+	ASSERT_EQ(vm->partition.dev_regions[0].page_count, 16);
+	ASSERT_EQ(vm->partition.dev_regions[0].attributes, 3);
+	ASSERT_EQ(vm->partition.dev_regions[0].smmu_id, 1);
+	ASSERT_EQ(vm->partition.dev_regions[0].stream_ids[0], 0);
+	ASSERT_EQ(vm->partition.dev_regions[0].stream_ids[1], 1);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[0].id, 2);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[0].attributes, 3);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[0].mpidr_valid, true);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[0].mpidr,
+		  0x123400005678);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[1].id, 4);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[1].attributes, 5);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[1].mpidr_valid, true);
+	ASSERT_EQ(vm->partition.dev_regions[0].interrupts[1].mpidr,
+		  0x1234567887654321);
+}
+
+TEST_F(manifest, ffa_invalid_interrupt_target_manifest)
+{
+	struct_manifest *m;
+
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.FfaValidManifest()
+		.StartChild("device-regions")
+			.Compatible({ "arm,ffa-manifest-device-regions" })
+			.StartChild("test-device")
+				.Description("test-device")
+				.Property("base-address", "<0x7400000>")
+				.Property("pages-count", "<16>")
+				.Property("attributes", "<3>")
+				.Property("smmu-id", "<1>")
+				.Property("stream-ids", "<0 1>")
+				.Property("interrupts", "<2 3>, <4 5>")
+				.Property("interrupts-target", "<20 0x1234 0x5678>")
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_INTERRUPT_ID_NOT_IN_LIST);
+}
+
 } /* namespace */