PSA FF-A: add partition manifest parsing

Add PSA FF-A partition manifest structure to VM manifest. Add FF-A
partition package structure (comprises header + image dtb + image
payload). Parse FF-A partition manifest DT if requested by the
is_ffa_partition DTS boolean.

This assumes Hafnium entry point is directly fed with the hypervisor
manifest pointer address (rather than the initrd image).

For a regular VM, the entry point is the start address of the VM
workspace allocated by Hafnium from the mem ranges provided in
hypervisor manifest. If the VM is an FF-A compliant partition,
the VM entry point is extrapolated from the FF-A partition manifest
(start of the workspace as SP header load address plus entry point
offset from partition DTS).

Change-Id: I6947646d0cd6eb3c4d97a31aa589244b365c8486
Signed-off-by: Olivier Deprez <olivier.deprez@arm.com>
Signed-off-by: Louis Mayencourt <louis.mayencourt@arm.com>
diff --git a/docs/Manifest.md b/docs/Manifest.md
index 0912a6d..12e1cb6 100644
--- a/docs/Manifest.md
+++ b/docs/Manifest.md
@@ -81,6 +81,10 @@
 };
 ```
 
+## PSA FF-A partition
+Partitions wishing to follow the PSA FF-A specification must respect the
+format specified by the [TF-A binding document](https://trustedfirmware-a.readthedocs.io/en/latest/components/psa-ffa-manifest-binding.html).
+
 ## Compiling
 
 Hafnium expects the manifest inside its [RAM disk](HafniumRamDisk.md),
diff --git a/inc/hf/ffa_internal.h b/inc/hf/ffa_internal.h
index 3393305..34603fd 100644
--- a/inc/hf/ffa_internal.h
+++ b/inc/hf/ffa_internal.h
@@ -22,10 +22,6 @@
 
 #include "vmapi/hf/ffa.h"
 
-#define FFA_VERSION_MAJOR 0x1
-#define FFA_VERSION_MINOR 0x0
-
-#define FFA_VERSION_MAJOR_OFFSET 16
 #define FFA_VERSION_RESERVED_BIT UINT32_C(1U << 31)
 
 static inline struct ffa_value ffa_error(uint64_t error_code)
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index ab3a92f..9286202 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include "hf/addr.h"
 #include "hf/ffa.h"
 #include "hf/memiter.h"
 #include "hf/string.h"
@@ -23,6 +24,103 @@
 
 #define MANIFEST_INVALID_ADDRESS UINT64_MAX
 
+#define SP_PKG_HEADER_MAGIC (0x474b5053)
+#define SP_PKG_HEADER_VERSION (0x1)
+
+#define SP_RTX_BUF_NAME_SIZE 10
+
+enum run_time_el {
+	EL1 = 0,
+	S_EL0,
+	S_EL1,
+	SUPERVISOR_MODE,
+	SECURE_USER_MODE,
+	SECURE_SUPERVISOR_MODE
+};
+
+enum execution_state { AARCH64 = 0, AARCH32 };
+
+enum xlat_granule { PAGE_4KB = 0, PAGE_16KB, PAGE_64KB };
+
+enum messaging_method {
+	DIRECT_MESSAGING = 0,
+	INDIRECT_MESSAGING,
+	BOTH_MESSAGING
+};
+
+/**
+ * Partition manifest as described in PSA FF-A v1.0 spec section 3.1
+ */
+struct sp_manifest {
+	/** PSA-FF-A expected version - mandatory */
+	uint32_t ffa_version;
+	/** UUID - mandatory */
+	uint32_t uuid[4];
+	/** Partition id - optional */
+	ffa_vm_id_t id;
+	/** Aux ids for mem transactions - optional */
+	ffa_vm_id_t aux_id;
+
+	/* NOTE: optional name field maps to VM debug_name field */
+
+	/** mandatory */
+	ffa_vcpu_count_t execution_ctx_count;
+	/** EL1 or secure EL1, secure EL0 - mandatory */
+	enum run_time_el run_time_el;
+	/** AArch32 / AArch64 - mandatory */
+	enum execution_state execution_state;
+	/** optional */
+	uintpaddr_t load_addr;
+	/** optional */
+	size_t ep_offset;
+	/**  4/16/64KB - mandatory */
+	enum xlat_granule xlat_granule;
+	/** optional */
+	uint16_t boot_order;
+
+	/** Optional RX/TX buffers */
+	struct {
+		bool rxtx_found;
+		/** optional */
+		uint64_t base_address;
+		/** optional */
+		uint16_t pages_count;
+		/** mandatory */
+		uint16_t attributes;
+		/** Optional */
+		char name[SP_RTX_BUF_NAME_SIZE];
+	} rxtx;
+
+	/** mandatory - direct/indirect msg or both */
+	enum messaging_method messaging_method;
+	/** optional */
+	bool has_primary_scheduler;
+	/** optional - preemptible / run to completion */
+	uint8_t runtime_model;
+	/** optional */
+	bool time_slice_mem;
+	/** optional - tuples SEPID/SMMUID/streamId */
+	uint32_t stream_ep_ids[1];
+};
+
+/**
+ *  Header for a PSA FF-A partition package.
+ */
+struct sp_pkg_header {
+	/** Magic used to identify a SP package. Value is "SPKG" */
+	uint32_t magic;
+	/** Version number of the header */
+	uint32_t version;
+	/** Offset in bytes to the partition manifest */
+	uint32_t pm_offset;
+	/** Size in bytes of the partition manifest */
+	uint32_t pm_size;
+	/** Offset in bytes to the base address of the partition binary */
+	uint32_t img_offset;
+	/** Size in bytes of the partition binary */
+	uint32_t img_size;
+};
+
 /**
  * Holds information about one of the VMs described in the manifest.
  */
@@ -31,6 +129,8 @@
 	struct string debug_name;
 	struct string kernel_filename;
 	struct smc_whitelist smc_whitelist;
+	bool is_ffa_partition;
+	struct sp_manifest sp;
 
 	union {
 		/* Properties specific to the primary VM. */
@@ -58,6 +158,7 @@
 enum manifest_return_code {
 	MANIFEST_SUCCESS = 0,
 	MANIFEST_ERROR_FILE_SIZE,
+	MANIFEST_ERROR_MALFORMED_DTB,
 	MANIFEST_ERROR_NO_ROOT_NODE,
 	MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE,
 	MANIFEST_ERROR_NOT_COMPATIBLE,
@@ -73,7 +174,11 @@
 	MANIFEST_ERROR_MALFORMED_BOOLEAN,
 };
 
-enum manifest_return_code manifest_init(struct manifest *manifest,
-					struct memiter *manifest_fdt);
+enum manifest_return_code manifest_init(struct mm_stage1_locked stage1_locked,
+					struct manifest *manifest,
+					struct memiter *manifest_fdt,
+					struct mpool *ppool);
+
+void manifest_dump(struct manifest_vm *vm);
 
 const char *manifest_strerror(enum manifest_return_code ret_code);
diff --git a/inc/vmapi/hf/ffa.h b/inc/vmapi/hf/ffa.h
index a955ca8..f7d7356 100644
--- a/inc/vmapi/hf/ffa.h
+++ b/inc/vmapi/hf/ffa.h
@@ -18,6 +18,11 @@
 
 #include "hf/types.h"
 
+#define FFA_VERSION_MAJOR 0x1
+#define FFA_VERSION_MINOR 0x0
+
+#define FFA_VERSION_MAJOR_OFFSET 16
+
 /* clang-format off */
 
 #define FFA_LOW_32_ID  0x84000060
diff --git a/src/init.c b/src/init.c
index 5652897..ccf496e 100644
--- a/src/init.c
+++ b/src/init.c
@@ -106,24 +106,39 @@
 			  pa_addr(params.mem_ranges[i].end) - 1);
 	}
 
-	dlog_info("Ramdisk range: %#x - %#x\n", pa_addr(params.initrd_begin),
-		  pa_addr(params.initrd_end) - 1);
+	/*
+	 * Hafnium manifest is either gathered from the ramdisk or passed
+	 * directly to Hafnium entry point by the earlier bootloader stage.
+	 * If the ramdisk start address is non-zero it hints the manifest
+	 * shall be looked up from the ramdisk. If zero, assume the address
+	 * passed to Hafnium entry point is the manifest address.
+	 */
+	if (pa_addr(params.initrd_begin)) {
+		dlog_info("Ramdisk range: %#x - %#x\n",
+			  pa_addr(params.initrd_begin),
+			  pa_addr(params.initrd_end) - 1);
 
-	/* Map initrd in, and initialise cpio parser. */
-	initrd = mm_identity_map(mm_stage1_locked, params.initrd_begin,
-				 params.initrd_end, MM_MODE_R, &ppool);
-	if (!initrd) {
-		panic("Unable to map initrd.");
+		/* Map initrd in, and initialise cpio parser. */
+		initrd = mm_identity_map(mm_stage1_locked, params.initrd_begin,
+					 params.initrd_end, MM_MODE_R, &ppool);
+		if (!initrd) {
+			panic("Unable to map initrd.");
+		}
+
+		memiter_init(
+			&cpio, initrd,
+			pa_difference(params.initrd_begin, params.initrd_end));
+
+		if (!cpio_get_file(&cpio, &manifest_fname, &manifest_it)) {
+			panic("Could not find manifest in initrd.");
+		}
+	} else {
+		manifest_it = fdt.buf;
 	}
 
-	memiter_init(&cpio, initrd,
-		     pa_difference(params.initrd_begin, params.initrd_end));
+	manifest_ret = manifest_init(mm_stage1_locked, &manifest, &manifest_it,
+				     &ppool);
 
-	if (!cpio_get_file(&cpio, &manifest_fname, &manifest_it)) {
-		panic("Could not find manifest in initrd.");
-	}
-
-	manifest_ret = manifest_init(&manifest, &manifest_it);
 	if (manifest_ret != MANIFEST_SUCCESS) {
 		panic("Could not parse manifest: %s.",
 		      manifest_strerror(manifest_ret));
diff --git a/src/load.c b/src/load.c
index 259726e..606848e 100644
--- a/src/load.c
+++ b/src/load.c
@@ -73,11 +73,6 @@
 {
 	struct memiter kernel;
 
-	if (string_is_empty(&manifest_vm->kernel_filename)) {
-		/* This signals the kernel has been preloaded. */
-		return true;
-	}
-
 	if (!cpio_get_file(cpio, &manifest_vm->kernel_filename, &kernel)) {
 		dlog_error("Could not find kernel file \"%s\".\n",
 			   string_data(&manifest_vm->kernel_filename));
@@ -119,22 +114,41 @@
 			 const struct memiter *cpio,
 			 const struct boot_params *params, struct mpool *ppool)
 {
+	paddr_t primary_begin;
+	ipaddr_t primary_entry;
 	struct vm *vm;
 	struct vm_locked vm_locked;
 	struct vcpu_locked vcpu_locked;
 	size_t i;
 	bool ret;
 
-	paddr_t primary_begin =
-		(manifest_vm->primary.boot_address == MANIFEST_INVALID_ADDRESS)
-			? layout_primary_begin()
-			: pa_init(manifest_vm->primary.boot_address);
+	if (manifest_vm->is_ffa_partition) {
+		primary_begin = pa_init(manifest_vm->sp.load_addr);
+		primary_entry = ipa_add(ipa_from_pa(primary_begin),
+					manifest_vm->sp.ep_offset);
+	} else {
+		primary_begin =
+			(manifest_vm->primary.boot_address ==
+			 MANIFEST_INVALID_ADDRESS)
+				? layout_primary_begin()
+				: pa_init(manifest_vm->primary.boot_address);
+		primary_entry = ipa_from_pa(primary_begin);
+	}
+
 	paddr_t primary_end = pa_add(primary_begin, RSIZE_MAX);
 
-	if (!load_kernel(stage1_locked, primary_begin, primary_end, manifest_vm,
-			 cpio, ppool)) {
-		dlog_error("Unable to load primary kernel.\n");
-		return false;
+	/*
+	 * Load the kernel if a filename is specified in the VM manifest.
+	 * For an FF-A partition, kernel_filename is undefined indicating
+	 * the partition package has already been loaded prior to Hafnium
+	 * booting.
+	 */
+	if (!string_is_empty(&manifest_vm->kernel_filename)) {
+		if (!load_kernel(stage1_locked, primary_begin, primary_end,
+				 manifest_vm, cpio, ppool)) {
+			dlog_error("Unable to load primary kernel.\n");
+			return false;
+		}
 	}
 
 	if (!vm_init_next(MAX_CPUS, ppool, &vm)) {
@@ -219,7 +233,7 @@
 		  vm->vcpu_count, pa_addr(primary_begin));
 
 	vcpu_locked = vcpu_lock(vm_get_vcpu(vm, 0));
-	vcpu_on(vcpu_locked, ipa_from_pa(primary_begin), params->kernel_arg);
+	vcpu_on(vcpu_locked, primary_entry, params->kernel_arg);
 	vcpu_unlock(&vcpu_locked);
 	ret = true;
 
@@ -243,10 +257,18 @@
 	ipaddr_t secondary_entry;
 	bool ret;
 
-	if (!load_kernel(stage1_locked, mem_begin, mem_end, manifest_vm, cpio,
-			 ppool)) {
-		dlog_error("Unable to load kernel.\n");
-		return false;
+	/*
+	 * Load the kernel if a filename is specified in the VM manifest.
+	 * For an FF-A partition, kernel_filename is undefined indicating
+	 * the partition package has already been loaded prior to Hafnium
+	 * booting.
+	 */
+	if (!string_is_empty(&manifest_vm->kernel_filename)) {
+		if (!load_kernel(stage1_locked, mem_begin, mem_end, manifest_vm,
+				 cpio, ppool)) {
+			dlog_error("Unable to load kernel.\n");
+			return false;
+		}
 	}
 
 	if (!vm_init_next(manifest_vm->secondary.vcpu_count, ppool, &vm)) {
@@ -272,6 +294,11 @@
 	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) {
+		secondary_entry =
+			ipa_add(secondary_entry, manifest_vm->sp.ep_offset);
+	}
+
 	vcpu = vm_get_vcpu(vm, 0);
 	vcpu_secondary_reset_and_start(vcpu, secondary_entry,
 				       pa_difference(mem_begin, mem_end));
@@ -424,10 +451,16 @@
 			  manifest_vm->debug_name);
 
 		mem_size = align_up(manifest_vm->secondary.mem_size, PAGE_SIZE);
-		if (!carve_out_mem_range(mem_ranges_available,
-					 params->mem_ranges_count, mem_size,
-					 &secondary_mem_begin,
-					 &secondary_mem_end)) {
+
+		if (manifest_vm->is_ffa_partition) {
+			secondary_mem_begin =
+				pa_init(manifest_vm->sp.load_addr);
+			secondary_mem_end =
+				pa_init(manifest_vm->sp.load_addr + mem_size);
+		} else if (!carve_out_mem_range(mem_ranges_available,
+						params->mem_ranges_count,
+						mem_size, &secondary_mem_begin,
+						&secondary_mem_end)) {
 			dlog_error("Not enough memory (%u bytes).\n", mem_size);
 			continue;
 		}
diff --git a/src/manifest.c b/src/manifest.c
index 460494c..30fee4e 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -156,6 +156,22 @@
 	return ret;
 }
 
+static enum manifest_return_code read_uint32(const struct fdt_node *node,
+					     const char *property,
+					     uint32_t *out)
+{
+	uint64_t value;
+
+	TRY(read_uint64(node, property, &value));
+
+	if (value > UINT32_MAX) {
+		return MANIFEST_ERROR_INTEGER_OVERFLOW;
+	}
+
+	*out = (uint32_t)value;
+	return MANIFEST_SUCCESS;
+}
+
 static enum manifest_return_code read_uint16(const struct fdt_node *node,
 					     const char *property,
 					     uint16_t *out)
@@ -172,6 +188,21 @@
 	return MANIFEST_SUCCESS;
 }
 
+static enum manifest_return_code read_uint8(const struct fdt_node *node,
+					    const char *property, uint8_t *out)
+{
+	uint64_t value;
+
+	TRY(read_uint64(node, property, &value));
+
+	if (value > UINT8_MAX) {
+		return MANIFEST_ERROR_INTEGER_OVERFLOW;
+	}
+
+	*out = (uint8_t)value;
+	return MANIFEST_SUCCESS;
+}
+
 struct uint32list_iter {
 	struct memiter mem_it;
 };
@@ -214,16 +245,16 @@
 	return MANIFEST_SUCCESS;
 }
 
-static enum manifest_return_code parse_vm(const struct fdt_node *node,
-					  struct manifest_vm *vm,
-					  ffa_vm_id_t vm_id)
+static enum manifest_return_code parse_vm_common(const struct fdt_node *node,
+						 struct manifest_vm *vm,
+						 ffa_vm_id_t vm_id)
 {
 	struct uint32list_iter smcs;
 	size_t idx;
 
+	TRY(read_bool(node, "is_ffa_partition", &vm->is_ffa_partition));
+
 	TRY(read_string(node, "debug_name", &vm->debug_name));
-	TRY(read_optional_string(node, "kernel_filename",
-				 &vm->kernel_filename));
 
 	TRY(read_optional_uint32list(node, "smc_whitelist", &smcs));
 	while (uint32list_has_next(&smcs) &&
@@ -239,24 +270,256 @@
 	TRY(read_bool(node, "smc_whitelist_permissive",
 		      &vm->smc_whitelist.permissive));
 
+	if (vm_id != HF_PRIMARY_VM_ID) {
+		TRY(read_uint64(node, "mem_size", &vm->secondary.mem_size));
+		TRY(read_uint16(node, "vcpu_count", &vm->secondary.vcpu_count));
+	}
+
+	return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code parse_vm(struct fdt_node *node,
+					  struct manifest_vm *vm,
+					  ffa_vm_id_t vm_id)
+{
+	TRY(read_optional_string(node, "kernel_filename",
+				 &vm->kernel_filename));
+
 	if (vm_id == HF_PRIMARY_VM_ID) {
 		TRY(read_optional_string(node, "ramdisk_filename",
 					 &vm->primary.ramdisk_filename));
 		TRY(read_optional_uint64(node, "boot_address",
 					 MANIFEST_INVALID_ADDRESS,
 					 &vm->primary.boot_address));
-	} else {
-		TRY(read_uint64(node, "mem_size", &vm->secondary.mem_size));
-		TRY(read_uint16(node, "vcpu_count", &vm->secondary.vcpu_count));
 	}
+
 	return MANIFEST_SUCCESS;
 }
 
+static enum manifest_return_code parse_ffa_manifest(struct fdt *fdt,
+						    struct manifest_vm *vm)
+{
+	unsigned int i = 0;
+	struct uint32list_iter uuid;
+	uint32_t uuid_word;
+	struct fdt_node root;
+	struct fdt_node ffa_node;
+	struct string rxtx_node_name = STRING_INIT("rx_tx-info");
+
+	if (!fdt_find_node(fdt, "/", &root)) {
+		return MANIFEST_ERROR_NO_ROOT_NODE;
+	}
+
+	/* Check "compatible" property. */
+	if (!fdt_is_compatible(&root, "arm,ffa-manifest-1.0")) {
+		return MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	TRY(read_uint32(&root, "ffa-version", &vm->sp.ffa_version));
+	dlog_verbose("  SP expected FF-A version %d.%d\n",
+		     vm->sp.ffa_version >> 16, vm->sp.ffa_version & 0xffff);
+
+	TRY(read_optional_uint32list(&root, "uuid", &uuid));
+
+	while (uint32list_has_next(&uuid) && i < 4) {
+		TRY(uint32list_get_next(&uuid, &uuid_word));
+		vm->sp.uuid[i] = uuid_word;
+		i++;
+	}
+	dlog_verbose("  SP UUID %#x-%x-%x_%x\n", vm->sp.uuid[0], vm->sp.uuid[1],
+		     vm->sp.uuid[2], vm->sp.uuid[3]);
+
+	TRY(read_uint16(&root, "execution-ctx-count",
+			&vm->sp.execution_ctx_count));
+	dlog_verbose("  SP number of execution context %d\n",
+		     vm->sp.execution_ctx_count);
+
+	TRY(read_uint8(&root, "exception-level",
+		       (uint8_t *)&vm->sp.run_time_el));
+	dlog_verbose("  SP run-time EL %d\n", vm->sp.run_time_el);
+
+	TRY(read_uint8(&root, "execution-state",
+		       (uint8_t *)&vm->sp.execution_state));
+	dlog_verbose("  SP execution state %d\n", vm->sp.execution_state);
+
+	TRY(read_uint64(&root, "load-address", &vm->sp.load_addr));
+	dlog_verbose("  SP load address %#x\n", vm->sp.load_addr);
+
+	TRY(read_uint64(&root, "entrypoint-offset", &vm->sp.ep_offset));
+	dlog_verbose("  SP entry point offset %#x\n", vm->sp.ep_offset);
+
+	TRY(read_uint8(&root, "xlat-granule", (uint8_t *)&vm->sp.xlat_granule));
+	dlog_verbose("  SP translation granule %d\n", vm->sp.xlat_granule);
+
+	ffa_node = root;
+	if (fdt_find_child(&ffa_node, &rxtx_node_name)) {
+		if (!fdt_is_compatible(&ffa_node,
+				       "arm,ffa-manifest-rx_tx-buffer")) {
+			return MANIFEST_ERROR_NOT_COMPATIBLE;
+		}
+
+		TRY(read_uint64(&ffa_node, "base-address",
+				&vm->sp.rxtx.base_address));
+
+		TRY(read_uint16(&ffa_node, "pages-count",
+				&vm->sp.rxtx.pages_count));
+
+		TRY(read_uint16(&ffa_node, "attributes",
+				&vm->sp.rxtx.attributes));
+
+		vm->sp.rxtx.rxtx_found = true;
+	}
+
+	TRY(read_uint8(&root, "messaging-method",
+		       (uint8_t *)&vm->sp.messaging_method));
+	dlog_verbose("  SP messaging method %d\n", vm->sp.messaging_method);
+
+	return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code sanity_check_ffa_manifest(
+	struct manifest_vm *vm)
+{
+	uint16_t ffa_version_major;
+	uint16_t ffa_version_minor;
+	enum manifest_return_code ret_code = MANIFEST_SUCCESS;
+	const char *error_string = "specified in manifest is unsupported";
+
+	/* ensure that the SPM version is compatible */
+	ffa_version_major =
+		(vm->sp.ffa_version & 0xffff0000) >> FFA_VERSION_MAJOR_OFFSET;
+	ffa_version_minor = vm->sp.ffa_version & 0xffff;
+
+	if (ffa_version_major != FFA_VERSION_MAJOR ||
+	    ffa_version_minor > FFA_VERSION_MINOR) {
+		dlog_error("FF-A partition manifest version %s: %d.%d\n",
+			   error_string, ffa_version_major, ffa_version_minor);
+		ret_code = MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	if (vm->sp.xlat_granule != PAGE_4KB) {
+		dlog_error("Translation granule %s: %d\n", error_string,
+			   vm->sp.xlat_granule);
+		ret_code = MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	if (vm->sp.execution_state != AARCH64) {
+		dlog_error("Execution state %s: %d\n", error_string,
+			   vm->sp.execution_state);
+		ret_code = MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	if (vm->sp.run_time_el != EL1 && vm->sp.run_time_el != S_EL1) {
+		dlog_error("Exception level %s: %d\n", error_string,
+			   vm->sp.run_time_el);
+		ret_code = MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	if (vm->sp.messaging_method != INDIRECT_MESSAGING) {
+		dlog_error("Messaging method %s: %x\n", error_string,
+			   vm->sp.messaging_method);
+		ret_code = MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	return ret_code;
+}
+
+static enum manifest_return_code parse_ffa_partition_package(
+	struct mm_stage1_locked stage1_locked, struct fdt_node *node,
+	struct manifest_vm *vm, ffa_vm_id_t vm_id, struct mpool *ppool)
+{
+	enum manifest_return_code ret = MANIFEST_ERROR_NOT_COMPATIBLE;
+	uintpaddr_t sp_pkg_addr;
+	paddr_t sp_pkg_start;
+	paddr_t sp_pkg_end;
+	struct sp_pkg_header *sp_pkg;
+	size_t sp_header_dtb_size;
+	paddr_t sp_dtb_addr;
+	struct fdt sp_fdt;
+
+	/*
+	 * This must have been hinted as being an FF-A partition,
+	 * return straight with failure if this is not the case.
+	 */
+	if (!vm->is_ffa_partition) {
+		return MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	TRY(read_uint64(node, "load_address", &sp_pkg_addr));
+	if (!is_aligned(sp_pkg_addr, PAGE_SIZE)) {
+		return MANIFEST_ERROR_NOT_COMPATIBLE;
+	}
+
+	/* Map top of SP package as a single page to extract the header */
+	sp_pkg_start = pa_init(sp_pkg_addr);
+	sp_pkg_end = pa_add(sp_pkg_start, PAGE_SIZE);
+	sp_pkg = mm_identity_map(stage1_locked, sp_pkg_start,
+				 pa_add(sp_pkg_start, PAGE_SIZE), MM_MODE_R,
+				 ppool);
+	CHECK(sp_pkg != NULL);
+
+	dlog_verbose("SP package load address %#x\n", sp_pkg_addr);
+
+	if (sp_pkg->magic != SP_PKG_HEADER_MAGIC) {
+		dlog_error("Invalid SP package magic.\n");
+		goto exit_unmap;
+	}
+
+	if (sp_pkg->version != SP_PKG_HEADER_VERSION) {
+		dlog_error("Invalid SP package version.\n");
+		goto exit_unmap;
+	}
+
+	/* Expect SP DTB to immediately follow header */
+	if (sp_pkg->pm_offset != sizeof(struct sp_pkg_header)) {
+		dlog_error("Invalid SP package manifest offset.\n");
+		goto exit_unmap;
+	}
+
+	sp_header_dtb_size = align_up(
+		sp_pkg->pm_size + sizeof(struct sp_pkg_header), PAGE_SIZE);
+	if ((vm_id != HF_PRIMARY_VM_ID) &&
+	    (sp_header_dtb_size >= vm->secondary.mem_size)) {
+		dlog_error("Invalid SP package header or DT size.\n");
+		goto exit_unmap;
+	}
+
+	if (sp_header_dtb_size > PAGE_SIZE) {
+		/* Map remainder of header + DTB  */
+		sp_pkg_end = pa_add(sp_pkg_start, sp_header_dtb_size);
+
+		sp_pkg = mm_identity_map(stage1_locked, sp_pkg_start,
+					 sp_pkg_end, MM_MODE_R, ppool);
+		CHECK(sp_pkg != NULL);
+	}
+
+	sp_dtb_addr = pa_add(sp_pkg_start, sp_pkg->pm_offset);
+	if (!fdt_init_from_ptr(&sp_fdt, (void *)sp_dtb_addr.pa,
+			       sp_pkg->pm_size)) {
+		dlog_error("FDT failed validation.\n");
+		goto exit_unmap;
+	}
+
+	ret = parse_ffa_manifest(&sp_fdt, vm);
+	if (ret != MANIFEST_SUCCESS) {
+		goto exit_unmap;
+	}
+
+	ret = sanity_check_ffa_manifest(vm);
+
+exit_unmap:
+	CHECK(mm_unmap(stage1_locked, sp_pkg_start, sp_pkg_end, ppool));
+
+	return ret;
+}
+
 /**
  * Parse manifest from FDT.
  */
-enum manifest_return_code manifest_init(struct manifest *manifest,
-					struct memiter *manifest_fdt)
+enum manifest_return_code manifest_init(struct mm_stage1_locked stage1_locked,
+					struct manifest *manifest,
+					struct memiter *manifest_fdt,
+					struct mpool *ppool)
 {
 	struct string vm_name;
 	struct fdt fdt;
@@ -313,7 +576,16 @@
 		}
 
 		manifest->vm_count = i + 1;
-		TRY(parse_vm(&vm_node, &manifest->vm[i], vm_id));
+
+		TRY(parse_vm_common(&vm_node, &manifest->vm[i], vm_id));
+
+		if (manifest->vm[i].is_ffa_partition) {
+			TRY(parse_ffa_partition_package(stage1_locked, &vm_node,
+							&manifest->vm[i], vm_id,
+							ppool));
+		} else {
+			TRY(parse_vm(&vm_node, &manifest->vm[i], vm_id));
+		}
 	}
 
 	if (!found_primary_vm) {
@@ -330,6 +602,8 @@
 		return "Success";
 	case MANIFEST_ERROR_FILE_SIZE:
 		return "Total size in header does not match file size";
+	case MANIFEST_ERROR_MALFORMED_DTB:
+		return "Malformed device tree blob";
 	case MANIFEST_ERROR_NO_ROOT_NODE:
 		return "Could not find root node in manifest";
 	case MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE:
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index bed1675..99665a2 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -203,6 +203,16 @@
 		return BooleanProperty("smc_whitelist_permissive");
 	}
 
+	ManifestDtBuilder &LoadAddress(uint64_t value)
+	{
+		return Integer64Property("load_address", value);
+	}
+
+	ManifestDtBuilder &FfaPartition()
+	{
+		return BooleanProperty("is_ffa_partition");
+	}
+
 	ManifestDtBuilder &Property(const std::string_view &name,
 				    const std::string_view &value)
 	{
@@ -279,9 +289,11 @@
 						   const std::vector<char> &vec)
 {
 	struct memiter it;
+	struct mpool ppool;
+	struct mm_stage1_locked mm_stage1_locked;
 
 	memiter_init(&it, vec.data(), vec.size());
-	return manifest_init(m, &it);
+	return manifest_init(mm_stage1_locked, m, &it, &ppool);
 }
 
 TEST(manifest, no_hypervisor_node)
@@ -611,4 +623,219 @@
 	ASSERT_FALSE(vm->smc_whitelist.permissive);
 }
 
+/**
+ * Class for programatically building a Partition package.
+ */
+class Partition_package
+{
+       public:
+	__attribute__((aligned(PAGE_SIZE))) struct sp_pkg_header spkg;
+	char manifest_dtb[PAGE_SIZE] = {};
+
+	Partition_package(const std::vector<char> &vec)
+	{
+		// Initialise header field
+		spkg.magic = SP_PKG_HEADER_MAGIC;
+		spkg.version = SP_PKG_HEADER_VERSION;
+		spkg.pm_offset = sizeof(struct sp_pkg_header);
+		spkg.pm_size = vec.size();
+
+		// Copy dtb into package
+		std::copy(vec.begin(), vec.end(), manifest_dtb);
+	}
+};
+
+static enum manifest_return_code ffa_manifest_from_vec(
+	struct manifest *m, const std::vector<char> &vec)
+{
+	struct memiter it;
+	struct mpool ppool;
+	struct mm_stage1_locked mm_stage1_locked;
+
+	Partition_package spkg(vec);
+
+	/* clang-format off */
+	std::vector<char> core_dtb = ManifestDtBuilder()
+		.StartChild("hypervisor")
+			.Compatible()
+			.StartChild("vm1")
+				.DebugName("primary_vm")
+				.FfaPartition()
+				.LoadAddress((uint64_t)&spkg)
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+
+	memiter_init(&it, core_dtb.data(), core_dtb.size());
+	return manifest_init(mm_stage1_locked, m, &it, &ppool);
+}
+
+TEST(manifest, ffa_not_compatible)
+{
+	struct manifest m;
+
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-2.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<2>")
+		.Property("execution-state", "<0>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<0>")
+		.Property("messaging-method", "<1>")
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+}
+
+TEST(manifest, ffa_missing_property)
+{
+	struct manifest m;
+
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_PROPERTY_NOT_FOUND);
+}
+
+TEST(manifest, ffa_validate_sanity_check)
+{
+	struct manifest m;
+
+	/* Incompatible version */
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0xa1>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<2>")
+		.Property("execution-state", "<0>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<0>")
+		.Property("messaging-method", "<1>")
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+
+	/* Incompatible translation granule */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<2>")
+		.Property("execution-state", "<0>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<3>")
+		.Property("messaging-method", "<1>")
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+
+	/* Incompatible exeption level */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<6>")
+		.Property("execution-state", "<0>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<0>")
+		.Property("messaging-method", "<1>")
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+
+	/* Incompatible execution state */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<2>")
+		.Property("execution-state", "<2>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<0>")
+		.Property("messaging-method", "<1>")
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+
+	/* Incompatible messaging method */
+	/* clang-format off */
+	dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<2>")
+		.Property("execution-state", "<0>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<0>")
+		.Property("messaging-method", "<3>")
+		.Build();
+	/* clang-format on */
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb),
+		  MANIFEST_ERROR_NOT_COMPATIBLE);
+}
+
+TEST(manifest, ffa_valid)
+{
+	struct manifest m;
+
+	/* clang-format off */
+	std::vector<char>  dtb = ManifestDtBuilder()
+		.Compatible({ "arm,ffa-manifest-1.0" })
+		.Property("ffa-version", "<0x10000>")
+		.Property("uuid", "<0xb4b5671e 0x4a904fe1 0xb81ffb13 0xdae1dacb>")
+		.Property("execution-ctx-count", "<1>")
+		.Property("exception-level", "<2>")
+		.Property("execution-state", "<0>")
+		.Property("load-address", "<0x7000000>")
+		.Property("entrypoint-offset", "<0x00001000>")
+		.Property("xlat-granule", "<0>")
+		.Property("messaging-method", "<1>")
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(ffa_manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
+
+	ASSERT_EQ(m.vm[0].sp.ffa_version, 0x10000);
+	ASSERT_THAT(
+		std::span(m.vm[0].sp.uuid, 4),
+		ElementsAre(0xb4b5671e, 0x4a904fe1, 0xb81ffb13, 0xdae1dacb));
+	ASSERT_EQ(m.vm[0].sp.execution_ctx_count, 1);
+	ASSERT_EQ(m.vm[0].sp.run_time_el, S_EL1);
+	ASSERT_EQ(m.vm[0].sp.execution_state, AARCH64);
+	ASSERT_EQ(m.vm[0].sp.load_addr, 0x7000000);
+	ASSERT_EQ(m.vm[0].sp.ep_offset, 0x00001000);
+	ASSERT_EQ(m.vm[0].sp.xlat_granule, PAGE_4KB);
+	ASSERT_EQ(m.vm[0].sp.messaging_method, INDIRECT_MESSAGING);
+}
+
 } /* namespace */