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