feat: manifest NS memory/device region attribute

This change permits providing the security state of a memory region for
S-EL0 partitions through Hafnium/VHE.
Note the same mechanism will be supported only from a later change for
S-EL1 partitions as the NS bit is controlled by the Stage-1 translation
regime. Hafnium must leverage VTTBR/VSTTBR page tables to enforce this.

Change-Id: Ieb0256d3c5f499dc435d5a2f95934f338178c315
Signed-off-by: Olivier Deprez <olivier.deprez@arm.com>
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index bcdd628..582f564 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -25,8 +25,15 @@
 #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
+/** FF-A manifest memory and device regions attributes. */
+#define MANIFEST_REGION_ATTR_READ (UINT32_C(1) << 0)
+#define MANIFEST_REGION_ATTR_WRITE (UINT32_C(1) << 1)
+#define MANIFEST_REGION_ATTR_EXEC (UINT32_C(1) << 2)
+#define MANIFEST_REGION_ATTR_SECURITY (UINT32_C(1) << 3)
+
+#define MANIFEST_REGION_ALL_ATTR_MASK                             \
+	(MANIFEST_REGION_ATTR_READ | MANIFEST_REGION_ATTR_WRITE | \
+	 MANIFEST_REGION_ATTR_EXEC | MANIFEST_REGION_ATTR_SECURITY)
 
 /* Highest possible value for the boot-order field. */
 #define DEFAULT_BOOT_ORDER 0xFFFF
diff --git a/src/load.c b/src/load.c
index bcf1730..770f860 100644
--- a/src/load.c
+++ b/src/load.c
@@ -444,6 +444,59 @@
 	return true;
 }
 
+/**
+ * Convert the manifest memory region attributes to mode consumed by mm layer.
+ */
+static uint32_t memory_region_attributes_to_mode(uint32_t attributes)
+{
+	uint32_t mode = 0U;
+
+	if ((attributes & MANIFEST_REGION_ATTR_READ) != 0U) {
+		mode |= MM_MODE_R;
+	}
+
+	if ((attributes & MANIFEST_REGION_ATTR_WRITE) != 0U) {
+		mode |= MM_MODE_W;
+	}
+
+	if ((attributes & MANIFEST_REGION_ATTR_EXEC) != 0U) {
+		mode |= MM_MODE_X;
+	}
+
+	assert((mode == (MM_MODE_R | MM_MODE_W)) || (mode == MM_MODE_R) ||
+	       (mode == (MM_MODE_R | MM_MODE_X)));
+
+	if ((attributes & MANIFEST_REGION_ATTR_SECURITY) != 0U) {
+		mode |= arch_mm_extra_attributes_from_vm(HF_HYPERVISOR_VM_ID);
+	}
+
+	return mode;
+}
+
+/**
+ * Convert the manifest device region attributes to mode consumed by mm layer.
+ */
+static uint32_t device_region_attributes_to_mode(uint32_t attributes)
+{
+	uint32_t mode = 0U;
+
+	if ((attributes & MANIFEST_REGION_ATTR_READ) != 0U) {
+		mode |= MM_MODE_R;
+	}
+
+	if ((attributes & MANIFEST_REGION_ATTR_WRITE) != 0U) {
+		mode |= MM_MODE_W;
+	}
+
+	assert((mode == (MM_MODE_R | MM_MODE_W)) || (mode == MM_MODE_R));
+
+	if ((attributes & MANIFEST_REGION_ATTR_SECURITY) != 0U) {
+		mode |= arch_mm_extra_attributes_from_vm(HF_HYPERVISOR_VM_ID);
+	}
+
+	return mode | MM_MODE_D;
+}
+
 /*
  * Loads a secondary VM.
  */
@@ -453,6 +506,7 @@
 			   const struct manifest_vm *manifest_vm,
 			   const struct memiter *cpio, struct mpool *ppool)
 {
+	const char *error_string = " region security state ignored for ";
 	struct vm *vm;
 	struct vm_locked vm_locked;
 	struct vcpu_locked vcpu_locked;
@@ -552,6 +606,7 @@
 		paddr_t alloc_base = mem_end;
 		size_t size;
 		size_t total_alloc = 0;
+		uint32_t attributes;
 
 		/* Map memory-regions */
 		while (j < manifest_vm->partition.mem_region_count) {
@@ -583,8 +638,29 @@
 				region_begin = pa_subtract(alloc_base, size);
 				alloc_base = region_begin;
 
-				map_mode = manifest_vm->partition.mem_regions[j]
-						   .attributes;
+				attributes =
+					manifest_vm->partition.mem_regions[j]
+						.attributes;
+				if ((attributes &
+				     MANIFEST_REGION_ATTR_SECURITY) != 0) {
+					if (plat_ffa_is_vm_id(
+						    vm_locked.vm->id)) {
+						dlog_warning("Memory%sVMs\n",
+							     error_string);
+						attributes &=
+							~MANIFEST_REGION_ATTR_SECURITY;
+					} else if (!vm->el0_partition) {
+						dlog_warning(
+							"Memory%sS-EL1 "
+							"partitions.\n",
+							error_string);
+						attributes &=
+							~MANIFEST_REGION_ATTR_SECURITY;
+					}
+				}
+
+				map_mode = memory_region_attributes_to_mode(
+					attributes);
 				if (vm->el0_partition) {
 					map_mode |= MM_MODE_USER | MM_MODE_NG;
 				}
@@ -612,8 +688,30 @@
 						.base_address);
 				region_end = pa_add(region_begin, size);
 
-				map_mode = manifest_vm->partition.mem_regions[j]
-						   .attributes;
+				attributes =
+					manifest_vm->partition.mem_regions[j]
+						.attributes;
+				if ((attributes &
+				     MANIFEST_REGION_ATTR_SECURITY) != 0) {
+					if (plat_ffa_is_vm_id(
+						    vm_locked.vm->id)) {
+						dlog_warning("Memory%sVMs\n",
+							     error_string);
+						attributes &=
+							~MANIFEST_REGION_ATTR_SECURITY;
+
+					} else if (!vm->el0_partition) {
+						dlog_warning(
+							"Memory%sS-EL1 "
+							"partitions.\n",
+							error_string);
+						attributes &=
+							~MANIFEST_REGION_ATTR_SECURITY;
+					}
+				}
+
+				map_mode = memory_region_attributes_to_mode(
+					attributes);
 				if (vm->el0_partition) {
 					map_mode |= MM_MODE_USER | MM_MODE_NG;
 				}
@@ -653,8 +751,25 @@
 			       PAGE_SIZE;
 			region_end = pa_add(region_begin, size);
 
-			map_mode = manifest_vm->partition.dev_regions[j]
-					   .attributes;
+			attributes = manifest_vm->partition.dev_regions[j]
+					     .attributes;
+			if ((attributes & MANIFEST_REGION_ATTR_SECURITY) != 0) {
+				if (plat_ffa_is_vm_id(vm_locked.vm->id)) {
+					dlog_warning("Device%sVMs\n",
+						     error_string);
+					attributes &=
+						~MANIFEST_REGION_ATTR_SECURITY;
+				} else if (!vm->el0_partition) {
+					dlog_warning(
+						"Device%sS-EL1 "
+						"partitions.\n",
+						error_string);
+					attributes &=
+						~MANIFEST_REGION_ATTR_SECURITY;
+				}
+			}
+
+			map_mode = device_region_attributes_to_mode(attributes);
 			if (vm->el0_partition) {
 				map_mode |= MM_MODE_USER | MM_MODE_NG;
 			}
diff --git a/src/manifest.c b/src/manifest.c
index 14e2df1..0b8a824 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -393,15 +393,27 @@
 
 		TRY(read_uint32(mem_node, "attributes",
 				&mem_regions[i].attributes));
-		mem_regions[i].attributes &= MM_PERM_MASK;
 
-		if (mem_regions[i].attributes != (MM_MODE_R) &&
-		    mem_regions[i].attributes != (MM_MODE_R | MM_MODE_W) &&
-		    mem_regions[i].attributes != (MM_MODE_R | MM_MODE_X)) {
+		/*
+		 * Check RWX permission attributes.
+		 * Security attribute is checked at load phase.
+		 */
+		uint32_t permissions = mem_regions[i].attributes &
+				       (MANIFEST_REGION_ATTR_READ |
+					MANIFEST_REGION_ATTR_WRITE |
+					MANIFEST_REGION_ATTR_EXEC);
+		if (permissions != MANIFEST_REGION_ATTR_READ &&
+		    permissions != (MANIFEST_REGION_ATTR_READ |
+				    MANIFEST_REGION_ATTR_WRITE) &&
+		    permissions != (MANIFEST_REGION_ATTR_READ |
+				    MANIFEST_REGION_ATTR_EXEC)) {
 			return MANIFEST_ERROR_INVALID_MEM_PERM;
 		}
 
-		dlog_verbose("      Attributes:  %u\n",
+		/* Filter memory region attributes. */
+		mem_regions[i].attributes &= MANIFEST_REGION_ALL_ATTR_MASK;
+
+		dlog_verbose("      Attributes:  %#x\n",
 			     mem_regions[i].attributes);
 
 		if (rxtx->available) {
@@ -468,16 +480,27 @@
 
 		TRY(read_uint32(dev_node, "attributes",
 				&dev_regions[i].attributes));
-		dev_regions[i].attributes =
-			(dev_regions[i].attributes & MM_PERM_MASK) | MM_MODE_D;
 
-		if (dev_regions[i].attributes != (MM_MODE_R | MM_MODE_D) &&
-		    dev_regions[i].attributes !=
-			    (MM_MODE_R | MM_MODE_W | MM_MODE_D)) {
+		/*
+		 * Check RWX permission attributes.
+		 * Security attribute is checked at load phase.
+		 */
+		uint32_t permissions = dev_regions[i].attributes &
+				       (MANIFEST_REGION_ATTR_READ |
+					MANIFEST_REGION_ATTR_WRITE |
+					MANIFEST_REGION_ATTR_EXEC);
+
+		if (permissions != MANIFEST_REGION_ATTR_READ &&
+		    permissions != (MANIFEST_REGION_ATTR_READ |
+				    MANIFEST_REGION_ATTR_WRITE)) {
 			return MANIFEST_ERROR_INVALID_MEM_PERM;
 		}
 
-		dlog_verbose("      Attributes:  %u\n",
+		/* Filer device region attributes. */
+		dev_regions[i].attributes = dev_regions[i].attributes &
+					    MANIFEST_REGION_ALL_ATTR_MASK;
+
+		dlog_verbose("      Attributes:  %#x\n",
 			     dev_regions[i].attributes);
 
 		TRY(read_optional_uint32list(dev_node, "interrupts", &list));
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index 159b0d5..beaf883 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -1116,6 +1116,12 @@
 				.Property("pages-count", "<4>")
 				.Property("attributes", "<3>")
 			.EndChild()
+			.StartChild("test-memory-ns")
+				.Description("test-memory")
+				.Property("base-address", "<0x7200000>")
+				.Property("pages-count", "<1>")
+				.Property("attributes", "<0xb>")
+			.EndChild()
 			.Label("rx")
 			.StartChild("rx")
 				.Description("rx-buffer")
@@ -1135,13 +1141,19 @@
 			.Compatible({ "arm,ffa-manifest-device-regions" })
 			.StartChild("test-device")
 				.Description("test-device")
-				.Property("base-address", "<0x7200000>")
+				.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>")
 			.EndChild()
+			.StartChild("test-device-ns")
+				.Description("test-device")
+				.Property("base-address", "<0x7500000>")
+				.Property("pages-count", "<1>")
+				.Property("attributes", "<0x9>")
+			.EndChild()
 		.EndChild()
 		.Build();
 	/* clang-format on */
@@ -1164,6 +1176,7 @@
 	ASSERT_EQ(m.vm[0].partition.mem_regions[0].base_address, 0x7100000);
 	ASSERT_EQ(m.vm[0].partition.mem_regions[0].page_count, 4);
 	ASSERT_EQ(m.vm[0].partition.mem_regions[0].attributes, 3);
+	ASSERT_EQ(m.vm[0].partition.mem_regions[1].attributes, (8 | 3));
 	ASSERT_EQ(m.vm[0].partition.rxtx.available, true);
 	ASSERT_EQ(m.vm[0].partition.rxtx.rx_buffer->base_address, 0x7300000);
 	ASSERT_EQ(m.vm[0].partition.rxtx.rx_buffer->page_count, 1);
@@ -1171,11 +1184,10 @@
 	ASSERT_EQ(m.vm[0].partition.rxtx.tx_buffer->base_address, 0x7310000);
 	ASSERT_EQ(m.vm[0].partition.rxtx.tx_buffer->page_count, 1);
 	ASSERT_EQ(m.vm[0].partition.rxtx.tx_buffer->attributes, 3);
-	ASSERT_EQ(m.vm[0].partition.dev_regions[0].base_address, 0x7200000);
+	ASSERT_EQ(m.vm[0].partition.dev_regions[0].base_address, 0x7400000);
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].page_count, 16);
 
-	/* Attribute is ORed with MM_MODE_D */
-	ASSERT_EQ(m.vm[0].partition.dev_regions[0].attributes, (3 | 8));
+	ASSERT_EQ(m.vm[0].partition.dev_regions[0].attributes, 3);
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].smmu_id, 1);
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].stream_ids[0], 0);
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].stream_ids[1], 1);
@@ -1183,6 +1195,7 @@
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].interrupts[0].attributes, 3);
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].interrupts[1].id, 4);
 	ASSERT_EQ(m.vm[0].partition.dev_regions[0].interrupts[1].attributes, 5);
+	ASSERT_EQ(m.vm[0].partition.dev_regions[1].attributes, (8 | 1));
 }
 
 } /* namespace */