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/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 */