PSA FF-A: add device-region parsing support

This patch implements support for parsing device-regions described in
Section 3.1, Table 11 of PSA FFA EAC specification.
A maximum of 8 device-regions can be defined for a given partition.

Change-Id: I671b727a771aaa348b2fbb140059c7f7bedc1c60
Signed-off-by: Manish Pandey <manish.pandey2@arm.com>
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index fc3307b..3d8dfb2 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -22,6 +22,9 @@
 #define SP_RTX_BUF_NAME_SIZE 10
 
 #define SP_MAX_MEMORY_REGIONS 8
+#define SP_MAX_DEVICE_REGIONS 8
+#define SP_MAX_INTERRUPTS_PER_DEVICE 4
+#define SP_MAX_STREAMS_PER_DEVICE 4
 
 /** Mask for getting read/write/execute permission */
 #define MM_PERM_MASK 0x7
@@ -62,6 +65,33 @@
 	struct string name;
 };
 
+struct interrupt {
+	uint32_t id;
+	uint32_t attributes;
+};
+
+/**
+ * Partition Device region as described in PSA FFA v1.0 spec, Table 11
+ */
+struct device_region {
+	/** Device base PA - mandatory */
+	uintptr_t base_address;
+	/** Page count - mandatory */
+	uint32_t page_count;
+	/** Memory attributes - mandatory */
+	uint32_t attributes;
+	/** List of physical interrupt ID's and their attributes - optional */
+	struct interrupt interrupts[SP_MAX_INTERRUPTS_PER_DEVICE];
+	/** SMMU ID - optional */
+	uint32_t smmu_id;
+	/** List of Stream IDs assigned to device - optional */
+	uint32_t stream_ids[SP_MAX_STREAMS_PER_DEVICE];
+	/** Exclusive access to an endpoint - optional */
+	bool exclusive_access;
+	/** Name of Device region - optional */
+	struct string name;
+};
+
 /**
  * Partition manifest as described in PSA FF-A v1.0 spec section 3.1
  */
@@ -118,6 +148,8 @@
 
 	/** Memory regions */
 	struct memory_region mem_regions[SP_MAX_MEMORY_REGIONS];
+	/** Device regions */
+	struct device_region dev_regions[SP_MAX_DEVICE_REGIONS];
 };
 
 /**
@@ -191,6 +223,7 @@
 	MANIFEST_ERROR_MALFORMED_INTEGER_LIST,
 	MANIFEST_ERROR_MALFORMED_BOOLEAN,
 	MANIFEST_ERROR_MEMORY_REGION_NODE_EMPTY,
+	MANIFEST_ERROR_DEVICE_REGION_NODE_EMPTY,
 };
 
 enum manifest_return_code manifest_init(struct mm_stage1_locked stage1_locked,
diff --git a/src/manifest.c b/src/manifest.c
index 879ed12..e5076b6 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -164,6 +164,20 @@
 	return MANIFEST_SUCCESS;
 }
 
+static enum manifest_return_code read_optional_uint32(
+	const struct fdt_node *node, const char *property,
+	uint32_t default_value, uint32_t *out)
+{
+	enum manifest_return_code ret;
+
+	ret = read_uint32(node, property, out);
+	if (ret == MANIFEST_ERROR_PROPERTY_NOT_FOUND) {
+		*out = default_value;
+		return MANIFEST_SUCCESS;
+	}
+	return ret;
+}
+
 static enum manifest_return_code read_uint16(const struct fdt_node *node,
 					     const char *property,
 					     uint16_t *out)
@@ -337,6 +351,107 @@
 	return MANIFEST_SUCCESS;
 }
 
+static enum manifest_return_code parse_ffa_device_region_node(
+	struct fdt_node *dev_node, struct device_region *dev_regions)
+{
+	struct uint32list_iter list;
+	unsigned int i = 0;
+	unsigned int j = 0;
+
+	dlog_verbose("  Partition Device Regions\n");
+
+	if (!fdt_is_compatible(dev_node, "arm,ffa-manifest-device-regions")) {
+		return MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	if (!fdt_first_child(dev_node)) {
+		return MANIFEST_ERROR_DEVICE_REGION_NODE_EMPTY;
+	}
+
+	do {
+		dlog_verbose("    Device Region[%u]\n", i);
+
+		TRY(read_optional_string(dev_node, "description",
+					 &dev_regions[i].name));
+		dlog_verbose("      Name: %s\n",
+			     string_data(&dev_regions[i].name));
+
+		TRY(read_uint64(dev_node, "base-address",
+				&dev_regions[i].base_address));
+		dlog_verbose("      Base address:  %#x\n",
+			     dev_regions[i].base_address);
+
+		TRY(read_uint32(dev_node, "pages-count",
+				&dev_regions[i].page_count));
+		dlog_verbose("      Pages_count:  %u\n",
+			     dev_regions[i].page_count);
+
+		TRY(read_uint32(dev_node, "attributes",
+				&dev_regions[i].attributes));
+		dev_regions[i].attributes =
+			(dev_regions[i].attributes & MM_PERM_MASK) | MM_MODE_D;
+		dlog_verbose("      Attributes:  %u\n",
+			     dev_regions[i].attributes);
+
+		TRY(read_optional_uint32list(dev_node, "interrupts", &list));
+		dlog_verbose("      Interrupt List:\n");
+		j = 0;
+		while (uint32list_has_next(&list) &&
+		       j < SP_MAX_INTERRUPTS_PER_DEVICE) {
+			TRY(uint32list_get_next(
+				&list, &dev_regions[i].interrupts[j].id));
+			if (uint32list_has_next(&list)) {
+				TRY(uint32list_get_next(&list,
+							&dev_regions[i]
+								 .interrupts[j]
+								 .attributes));
+			} else {
+				return MANIFEST_ERROR_MALFORMED_INTEGER_LIST;
+			}
+
+			dlog_verbose("        ID = %u, attributes = %u\n",
+				     dev_regions[i].interrupts[j].id,
+				     dev_regions[i].interrupts[j].attributes);
+			j++;
+		}
+		if (j == 0) {
+			dlog_verbose("        Empty\n");
+		}
+
+		TRY(read_optional_uint32(dev_node, "smmu-id",
+					 (uint32_t)MANIFEST_INVALID_ADDRESS,
+					 &dev_regions[i].smmu_id));
+		dlog_verbose("      smmu-id:  %u\n", dev_regions[i].smmu_id);
+
+		TRY(read_optional_uint32list(dev_node, "stream-ids", &list));
+		dlog_verbose("      Stream IDs assigned:\n");
+
+		j = 0;
+		while (uint32list_has_next(&list) &&
+		       j < SP_MAX_STREAMS_PER_DEVICE) {
+			TRY(uint32list_get_next(&list,
+						&dev_regions[i].stream_ids[j]));
+			dlog_verbose("        %u\n",
+				     dev_regions[i].stream_ids[j]);
+			j++;
+		}
+		if (j == 0) {
+			dlog_verbose("        None\n");
+		}
+
+		TRY(read_bool(dev_node, "exclusive-access",
+			      &dev_regions[i].exclusive_access));
+		dlog_verbose("      Exclusive_access: %d\n",
+			     dev_regions[i].exclusive_access);
+
+		i++;
+	} while (fdt_next_sibling(dev_node) && (i < SP_MAX_DEVICE_REGIONS));
+
+	dlog_verbose("    Total %u device regions found\n", i);
+
+	return MANIFEST_SUCCESS;
+}
+
 static enum manifest_return_code parse_ffa_manifest(struct fdt *fdt,
 						    struct manifest_vm *vm)
 {
@@ -347,6 +462,7 @@
 	struct fdt_node ffa_node;
 	struct string rxtx_node_name = STRING_INIT("rx_tx-info");
 	struct string mem_region_node_name = STRING_INIT("memory-regions");
+	struct string dev_region_node_name = STRING_INIT("device-regions");
 
 	if (!fdt_find_node(fdt, "/", &root)) {
 		return MANIFEST_ERROR_NO_ROOT_NODE;
@@ -424,6 +540,13 @@
 						 vm->sp.mem_regions));
 	}
 
+	/* Parse Device-regions */
+	ffa_node = root;
+	if (fdt_find_child(&ffa_node, &dev_region_node_name)) {
+		TRY(parse_ffa_device_region_node(&ffa_node,
+						 vm->sp.dev_regions));
+	}
+
 	return MANIFEST_SUCCESS;
 }
 
@@ -683,6 +806,8 @@
 		return "Malformed boolean property";
 	case MANIFEST_ERROR_MEMORY_REGION_NODE_EMPTY:
 		return "Memory-region node should have at least one entry";
+	case MANIFEST_ERROR_DEVICE_REGION_NODE_EMPTY:
+		return "Device-region node should have at least one entry";
 	}
 
 	panic("Unexpected manifest return code.");
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index fcd109e..d58cb70 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -861,6 +861,71 @@
 		  MANIFEST_ERROR_PROPERTY_NOT_FOUND);
 }
 
+TEST(manifest, ffa_validate_dev_regions)
+{
+	struct manifest m;
+
+	/* Not Compatible */
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.FfaValidManifest()
+		.StartChild("device-regions")
+			.Compatible({ "foo,bar" })
+		.EndChild()
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+
+	/* Memory regions unavailable  */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.FfaValidManifest()
+		.StartChild("device-regions")
+			.Compatible({ "arm,ffa-manifest-device-regions" })
+		.EndChild()
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_DEVICE_REGION_NODE_EMPTY);
+
+	/* Missing Properties */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.FfaValidManifest()
+		.StartChild("device-regions")
+			.Compatible({ "arm,ffa-manifest-device-regions" })
+			.StartChild("test-device")
+				.Description("test-device")
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_PROPERTY_NOT_FOUND);
+
+	/* Malformed interrupt list pair */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.FfaValidManifest()
+		.StartChild("device-regions")
+			.Compatible({ "arm,ffa-manifest-device-regions" })
+			.StartChild("test-device")
+				.Description("test-device")
+				.Property("base-address", "<0x7200000>")
+				.Property("pages-count", "<16>")
+				.Property("attributes", "<3>")
+				.Property("smmu-id", "<1>")
+				.Property("stream-ids", "<0 1>")
+				.Property("interrupts", "<2 3>, <4>")
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_MALFORMED_INTEGER_LIST);
+}
+
 TEST(manifest, ffa_valid)
 {
 	struct manifest m;
@@ -877,6 +942,18 @@
 				.Property("attributes", "<7>")
 			.EndChild()
 		.EndChild()
+		.StartChild("device-regions")
+			.Compatible({ "arm,ffa-manifest-device-regions" })
+			.StartChild("test-device")
+				.Description("test-device")
+				.Property("base-address", "<0x7200000>")
+				.Property("pages-count", "<16>")
+				.Property("attributes", "<3>")
+				.Property("smmu-id", "<1>")
+				.Property("stream-ids", "<0 1>")
+				.Property("interrupts", "<2 3>, <4 5>")
+			.EndChild()
+		.EndChild()
 		.Build();
 	/* clang-format on */
 
@@ -896,6 +973,17 @@
 	ASSERT_EQ(m.vm[0].sp.mem_regions[0].base_address, 0x7100000);
 	ASSERT_EQ(m.vm[0].sp.mem_regions[0].page_count, 4);
 	ASSERT_EQ(m.vm[0].sp.mem_regions[0].attributes, 7);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].base_address, 0x7200000);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].page_count, 16);
+	/* Attribute is ORed with MM_MODE_D */
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].attributes, (3 | 8));
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].smmu_id, 1);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].stream_ids[0], 0);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].stream_ids[1], 1);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].interrupts[0].id, 2);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].interrupts[0].attributes, 3);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].interrupts[1].id, 4);
+	ASSERT_EQ(m.vm[0].sp.dev_regions[0].interrupts[1].attributes, 5);
 }
 
 } /* namespace */