PSA FF-A: map secondary VM memory/device regions in its stage-2 mapping

Add support for mapping secondary VM's memory/device regions in its
stage-2 translation along with unmapping it from primary VM as all the
regions are by default mapped in primary VM's translation regime.

As per PSA FF-A EAC spec, if memory-region does not have base-address
specified, the memory-region of given size is allocated inside the
partition's translation regime. This patch allocates memory-region at
the end of VM's memory. At the max half the VM's memory can be allocated
to memory regions.

When base-address is present this field specify a PA, VA (for S-EL0
partitions) or IPA (for S-EL1 and EL1 partitions). In both cases this
patch identity maps the region.

Change-Id: Id53e66d2109f05e76f8b8dbe85e64c84eabd5a5d
Signed-off-by: Manish Pandey <manish.pandey2@arm.com>
diff --git a/inc/hf/addr.h b/inc/hf/addr.h
index efb1844..59e1b7a 100644
--- a/inc/hf/addr.h
+++ b/inc/hf/addr.h
@@ -55,6 +55,14 @@
 }
 
 /**
+ * Move backward physical address.
+ */
+static inline paddr_t pa_subtract(paddr_t pa, size_t n)
+{
+	return pa_init(pa_addr(pa) - n);
+}
+
+/**
  * Returns the difference between two physical addresses.
  */
 static inline size_t pa_difference(paddr_t start, paddr_t end)
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index 1299c5e..dc4b7c7 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -149,8 +149,10 @@
 	uint32_t stream_ep_ids[1];
 
 	/** Memory regions */
+	uint8_t mem_region_count;
 	struct memory_region mem_regions[SP_MAX_MEMORY_REGIONS];
 	/** Device regions */
+	uint8_t dev_region_count;
 	struct device_region dev_regions[SP_MAX_DEVICE_REGIONS];
 };
 
diff --git a/src/load.c b/src/load.c
index 8962d65..26a9641 100644
--- a/src/load.c
+++ b/src/load.c
@@ -307,6 +307,7 @@
  * Loads a secondary VM.
  */
 static bool load_secondary(struct mm_stage1_locked stage1_locked,
+			   struct vm_locked primary_vm_locked,
 			   paddr_t mem_begin, paddr_t mem_end,
 			   const struct manifest_vm *manifest_vm,
 			   const struct memiter *cpio, struct mpool *ppool)
@@ -380,14 +381,132 @@
 		goto out;
 	}
 
-	dlog_info("Loaded with %u vCPUs, entry at %#x.\n",
-		  manifest_vm->secondary.vcpu_count, pa_addr(mem_begin));
-
 	if (manifest_vm->is_ffa_partition) {
+		int j = 0;
+		paddr_t region_begin;
+		paddr_t region_end;
+		paddr_t alloc_base = mem_end;
+		size_t size;
+		size_t total_alloc = 0;
+
+		/* Map memory-regions */
+		while (j < manifest_vm->sp.mem_region_count) {
+			size = manifest_vm->sp.mem_regions[j].page_count *
+			       PAGE_SIZE;
+			/*
+			 * For memory-regions without base-address, memory
+			 * should be allocated inside partition's page table.
+			 * Start allocating memory regions in partition's
+			 * page table, starting from the end.
+			 * TODO: Add mechanism to let partition know of these
+			 * memory regions
+			 */
+			if (manifest_vm->sp.mem_regions[j].base_address ==
+			    MANIFEST_INVALID_ADDRESS) {
+				total_alloc += size;
+				/* Don't go beyond half the VM's memory space */
+				if (total_alloc >
+				    (manifest_vm->secondary.mem_size / 2)) {
+					dlog_error(
+						"Not enough space for memory-"
+						"region allocation");
+					ret = false;
+					goto out;
+				}
+
+				region_end = alloc_base;
+				region_begin = pa_subtract(alloc_base, size);
+				alloc_base = region_begin;
+
+				if (!vm_identity_map(
+					    vm_locked, region_begin, region_end,
+					    manifest_vm->sp.mem_regions[j]
+						    .attributes,
+					    ppool, NULL)) {
+					dlog_error(
+						"Unable to map secondary VM "
+						"memory-region.\n");
+					ret = false;
+					goto out;
+				}
+
+				dlog_info(
+					"  Memory region %#x - %#x allocated\n",
+					region_begin, region_end);
+			} else {
+				/*
+				 * Identity map memory region for both case,
+				 * VA(S-EL0) or IPA(S-EL1).
+				 */
+				region_begin =
+					pa_init(manifest_vm->sp.mem_regions[j]
+							.base_address);
+				region_end = pa_add(region_begin, size);
+
+				if (!vm_identity_map(
+					    vm_locked, region_begin, region_end,
+					    manifest_vm->sp.mem_regions[j]
+						    .attributes,
+					    ppool, NULL)) {
+					dlog_error(
+						"Unable to map secondary VM "
+						"memory-region.\n");
+					ret = false;
+					goto out;
+				}
+			}
+
+			/* Deny the primary VM access to this memory */
+			if (!vm_unmap(primary_vm_locked, region_begin,
+				      region_end, ppool)) {
+				dlog_error(
+					"Unable to unmap secondary VM memory-"
+					"region from primary VM.\n");
+				ret = false;
+				goto out;
+			}
+
+			j++;
+		}
+
+		/* Map device-regions */
+		j = 0;
+		while (j < manifest_vm->sp.dev_region_count) {
+			region_begin = pa_init(
+				manifest_vm->sp.dev_regions[j].base_address);
+			size = manifest_vm->sp.dev_regions[j].page_count *
+			       PAGE_SIZE;
+			region_end = pa_add(region_begin, size);
+
+			if (!vm_identity_map(
+				    vm_locked, region_begin, region_end,
+				    manifest_vm->sp.dev_regions[j].attributes,
+				    ppool, NULL)) {
+				dlog_error(
+					"Unable to map secondary VM "
+					"device-region.\n");
+				ret = false;
+				goto out;
+			}
+			/* Deny primary VM access to this region */
+			if (!vm_unmap(primary_vm_locked, region_begin,
+				      region_end, ppool)) {
+				dlog_error(
+					"Unable to unmap secondary VM device-"
+					"region from primary VM.\n");
+				ret = false;
+				goto out;
+			}
+			j++;
+		}
+
 		secondary_entry =
 			ipa_add(secondary_entry, manifest_vm->sp.ep_offset);
 	}
 
+	dlog_info("Loaded with %u vCPUs, entry at %#x.\n",
+		  manifest_vm->secondary.vcpu_count, pa_addr(mem_begin));
+
 	vcpu = vm_get_vcpu(vm, 0);
 
 	if (has_fdt) {
@@ -565,9 +684,9 @@
 			continue;
 		}
 
-		if (!load_secondary(stage1_locked, secondary_mem_begin,
-				    secondary_mem_end, manifest_vm, cpio,
-				    ppool)) {
+		if (!load_secondary(stage1_locked, primary_vm_locked,
+				    secondary_mem_begin, secondary_mem_end,
+				    manifest_vm, cpio, ppool)) {
 			dlog_error("Unable to load VM.\n");
 			continue;
 		}
diff --git a/src/manifest.c b/src/manifest.c
index 9c98b5f..da25156 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -306,10 +306,10 @@
 
 static enum manifest_return_code parse_ffa_memory_region_node(
 	struct fdt_node *mem_node, struct memory_region *mem_regions,
-	struct rx_tx *rxtx)
+	uint8_t *count, struct rx_tx *rxtx)
 {
 	uint32_t phandle;
-	unsigned int i = 0;
+	uint8_t i = 0;
 
 	dlog_verbose("  Partition memory regions\n");
 
@@ -360,17 +360,18 @@
 		i++;
 	} while (fdt_next_sibling(mem_node) && (i < SP_MAX_MEMORY_REGIONS));
 
-	dlog_verbose("    Total %u memory regions found\n", i);
+	*count = i;
 
 	return MANIFEST_SUCCESS;
 }
 
 static enum manifest_return_code parse_ffa_device_region_node(
-	struct fdt_node *dev_node, struct device_region *dev_regions)
+	struct fdt_node *dev_node, struct device_region *dev_regions,
+	uint8_t *count)
 {
 	struct uint32list_iter list;
-	unsigned int i = 0;
-	unsigned int j = 0;
+	uint8_t i = 0;
+	uint8_t j = 0;
 
 	dlog_verbose("  Partition Device Regions\n");
 
@@ -461,7 +462,7 @@
 		i++;
 	} while (fdt_next_sibling(dev_node) && (i < SP_MAX_DEVICE_REGIONS));
 
-	dlog_verbose("    Total %u device regions found\n", i);
+	*count = i;
 
 	return MANIFEST_SUCCESS;
 }
@@ -552,15 +553,20 @@
 	ffa_node = root;
 	if (fdt_find_child(&ffa_node, &mem_region_node_name)) {
 		TRY(parse_ffa_memory_region_node(&ffa_node, vm->sp.mem_regions,
+						 &vm->sp.mem_region_count,
 						 &vm->sp.rxtx));
 	}
+	dlog_verbose("  Total %u memory regions found\n",
+		     vm->sp.mem_region_count);
 
 	/* 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));
+		TRY(parse_ffa_device_region_node(&ffa_node, vm->sp.dev_regions,
+						 &vm->sp.dev_region_count));
 	}
+	dlog_verbose("  Total %u device regions found\n",
+		     vm->sp.dev_region_count);
 
 	return MANIFEST_SUCCESS;
 }