Pass an FDT to the unit test VMs
Instead of passing the assigned memory size, optionally pass the
FDT to the secondary VMs. The VMs can extract the memory size, as
well as other potentially useful information from the FDT.
This is done to facilitate using the same unit test framework
with kvm-unit-tests, which expects an FDT, as does the Linux
kernel.
Change-Id: I436d86b3f4d1540c1995e01e48289f39e2c23490
Signed-off-by: Fuad Tabba <tabba@google.com>
diff --git a/src/fdt_handler.c b/src/fdt_handler.c
index fc62c5c..4d0496f 100644
--- a/src/fdt_handler.c
+++ b/src/fdt_handler.c
@@ -16,6 +16,22 @@
#include "hf/std.h"
/**
+ * Initializes the FDT struct with the pointer to the FDT data (header) in
+ * fdt_ptr.
+ */
+bool fdt_struct_from_ptr(const void *fdt_ptr, struct fdt *fdt)
+{
+ size_t fdt_size;
+
+ if (!fdt_ptr || !fdt) {
+ return false;
+ }
+
+ return fdt_size_from_header(fdt_ptr, &fdt_size) &&
+ fdt_init_from_ptr(fdt, fdt_ptr, fdt_size);
+}
+
+/**
* Finds the memory region where initrd is stored.
*/
bool fdt_find_initrd(const struct fdt *fdt, paddr_t *begin, paddr_t *end)
@@ -96,7 +112,8 @@
return true;
}
-bool fdt_find_memory_ranges(const struct fdt *fdt, struct string *device_type,
+bool fdt_find_memory_ranges(const struct fdt *fdt,
+ const struct string *device_type,
struct mem_range *mem_ranges,
size_t *mem_ranges_count, size_t mem_range_limit)
{
@@ -212,3 +229,30 @@
fdt_fini(fdt);
return true;
}
+
+/**
+ * Gets the size of the first memory range from the FDT into size.
+ *
+ * The test framework expects the address space to be contiguous, therefore
+ * gets the size of the first memory range, if there is more than one range.
+ */
+bool fdt_get_memory_size(const struct fdt *fdt, size_t *size)
+{
+ const struct string memory_device_type = STRING_INIT("memory");
+ struct mem_range mem_range;
+ size_t mem_ranges_count;
+
+ if (!fdt || !size ||
+ !fdt_find_memory_ranges(fdt, &memory_device_type, &mem_range,
+ &mem_ranges_count, 1)) {
+ return false;
+ }
+
+ if (mem_ranges_count < 1) {
+ return false;
+ }
+
+ *size = pa_difference(mem_range.begin, mem_range.end);
+
+ return true;
+}
diff --git a/src/fdt_patch.c b/src/fdt_patch.c
index 439a79f..0a4a045 100644
--- a/src/fdt_patch.c
+++ b/src/fdt_patch.c
@@ -156,3 +156,89 @@
ppool);
return false;
}
+
+bool fdt_patch_mem(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
+ size_t fdt_max_size, paddr_t mem_begin, paddr_t mem_end,
+ struct mpool *ppool)
+{
+ int ret = 0;
+ uint64_t mem_start_addr = pa_addr(mem_begin);
+ size_t mem_size = pa_difference(mem_begin, mem_end);
+ struct fdt_header *fdt;
+ int fdt_memory_node;
+ int root_offset;
+
+ /* Map the fdt in r/w mode in preparation for updating it. */
+ fdt = mm_identity_map(stage1_locked, fdt_addr,
+ pa_add(fdt_addr, fdt_max_size),
+ MM_MODE_R | MM_MODE_W, ppool);
+
+ if (!fdt) {
+ dlog_error("Unable to map FDT in r/w mode.\n");
+ return false;
+ }
+
+ ret = fdt_check_full(fdt, fdt_max_size);
+ if (ret != 0) {
+ dlog_error("FDT failed validation. Error: %d\n", ret);
+ goto out_unmap_fdt;
+ }
+
+ /* Allow some extra room for patches to the FDT. */
+ ret = fdt_open_into(fdt, fdt, fdt_max_size);
+ if (ret != 0) {
+ dlog_error("FDT failed to open_into. Error: %d\n", ret);
+ goto out_unmap_fdt;
+ }
+
+ root_offset = fdt_path_offset(fdt, "/");
+ if (ret < 0) {
+ dlog_error("FDT cannot find root offset. Error: %d\n", ret);
+ goto out_unmap_fdt;
+ }
+
+ /* Add a node to hold the memory information. */
+ fdt_memory_node = fdt_add_subnode(fdt, root_offset, "memory");
+ if (fdt_memory_node < 0) {
+ ret = fdt_memory_node;
+ dlog_error("FDT cannot add memory node. Error: %d\n", ret);
+ goto out_unmap_fdt;
+ }
+
+ /* Set the values for the VM's memory in the FDT. */
+ ret = fdt_appendprop_addrrange(fdt, root_offset, fdt_memory_node, "reg",
+ mem_start_addr, mem_size);
+ if (ret != 0) {
+ dlog_error(
+ "FDT failed to append address range property for "
+ "memory. Error: %d\n",
+ ret);
+ goto out_unmap_fdt;
+ }
+
+ ret = fdt_appendprop_string(fdt, fdt_memory_node, "device_type",
+ "memory");
+ if (ret != 0) {
+ dlog_error(
+ "FDT failed to append device_type property for memory. "
+ "Error: %d\n",
+ ret);
+ goto out_unmap_fdt;
+ }
+
+ ret = fdt_pack(fdt);
+ if (ret != 0) {
+ dlog_error("Failed to pack FDT. Error: %d\n", ret);
+ goto out_unmap_fdt;
+ }
+
+out_unmap_fdt:
+ /* Unmap FDT. */
+ if (!mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, fdt_max_size),
+ ppool)) {
+ dlog_error("Unable to unmap writable FDT.\n");
+ return false;
+ }
+
+ return ret == 0;
+}
diff --git a/src/load.c b/src/load.c
index f855a56..83eed53 100644
--- a/src/load.c
+++ b/src/load.c
@@ -16,6 +16,7 @@
#include "hf/boot_params.h"
#include "hf/check.h"
#include "hf/dlog.h"
+#include "hf/fdt_patch.h"
#include "hf/layout.h"
#include "hf/memiter.h"
#include "hf/mm.h"
@@ -59,11 +60,18 @@
return true;
}
+/**
+ * Loads the secondary VM's kernel.
+ * Stores the kernel size in kernel_size (if kernel_size is not NULL).
+ * Returns false if it cannot load the kernel.
+ */
static bool load_kernel(struct mm_stage1_locked stage1_locked, paddr_t begin,
paddr_t end, const struct manifest_vm *manifest_vm,
- const struct memiter *cpio, struct mpool *ppool)
+ const struct memiter *cpio, struct mpool *ppool,
+ size_t *kernel_size)
{
struct memiter kernel;
+ size_t size;
if (!cpio_get_file(cpio, &manifest_vm->kernel_filename, &kernel)) {
dlog_error("Could not find kernel file \"%s\".\n",
@@ -71,7 +79,8 @@
return false;
}
- if (pa_difference(begin, end) < memiter_size(&kernel)) {
+ size = memiter_size(&kernel);
+ if (pa_difference(begin, end) < size) {
dlog_error("Kernel is larger than available memory.\n");
return false;
}
@@ -81,6 +90,10 @@
return false;
}
+ if (kernel_size) {
+ *kernel_size = size;
+ }
+
return true;
}
@@ -137,7 +150,7 @@
*/
if (!string_is_empty(&manifest_vm->kernel_filename)) {
if (!load_kernel(stage1_locked, primary_begin, primary_end,
- manifest_vm, cpio, ppool)) {
+ manifest_vm, cpio, ppool, NULL)) {
dlog_error("Unable to load primary kernel.\n");
return false;
}
@@ -235,6 +248,60 @@
return ret;
}
+/**
+ * Loads the secondary VM's FDT.
+ * Stores the total allocated size for the FDT in fdt_allocated_size (if
+ * fdt_allocated_size is not NULL). The allocated size includes additional space
+ * for potential patching.
+ */
+static bool load_secondary_fdt(struct mm_stage1_locked stage1_locked,
+ paddr_t end, size_t fdt_max_size,
+ const struct manifest_vm *manifest_vm,
+ const struct memiter *cpio, struct mpool *ppool,
+ paddr_t *fdt_addr, size_t *fdt_allocated_size)
+{
+ struct memiter fdt;
+ size_t allocated_size;
+
+ CHECK(!string_is_empty(&manifest_vm->secondary.fdt_filename));
+
+ if (!cpio_get_file(cpio, &manifest_vm->secondary.fdt_filename, &fdt)) {
+ dlog_error("Cannot open the secondary VM's FDT.\n");
+ return false;
+ }
+
+ /*
+ * Ensure the FDT has one additional page at the end for patching, and
+ * and align it to the page boundary.
+ */
+ allocated_size = align_up(memiter_size(&fdt), PAGE_SIZE) + PAGE_SIZE;
+
+ if (allocated_size > fdt_max_size) {
+ dlog_error(
+ "FDT allocated space (%u) is more than the specified "
+ "maximum to use (%u).\n",
+ allocated_size, fdt_max_size);
+ return false;
+ }
+
+ /* Load the FDT to the end of the VM's allocated memory space. */
+ *fdt_addr = pa_init(pa_addr(pa_sub(end, allocated_size)));
+
+ dlog_info("Loading secondary FDT of allocated size %u at 0x%x.\n",
+ allocated_size, pa_addr(*fdt_addr));
+
+ if (!copy_to_unmapped(stage1_locked, *fdt_addr, &fdt, ppool)) {
+ dlog_error("Unable to copy FDT.\n");
+ return false;
+ }
+
+ if (fdt_allocated_size) {
+ *fdt_allocated_size = allocated_size;
+ }
+
+ return true;
+}
+
/*
* Loads a secondary VM.
*/
@@ -248,6 +315,10 @@
struct vcpu *vcpu;
ipaddr_t secondary_entry;
bool ret;
+ paddr_t fdt_addr;
+ bool has_fdt;
+ size_t kernel_size = 0;
+ const size_t mem_size = pa_difference(mem_begin, mem_end);
/*
* Load the kernel if a filename is specified in the VM manifest.
@@ -257,12 +328,37 @@
*/
if (!string_is_empty(&manifest_vm->kernel_filename)) {
if (!load_kernel(stage1_locked, mem_begin, mem_end, manifest_vm,
- cpio, ppool)) {
+ cpio, ppool, &kernel_size)) {
dlog_error("Unable to load kernel.\n");
return false;
}
}
+ has_fdt = !string_is_empty(&manifest_vm->secondary.fdt_filename);
+ if (has_fdt) {
+ /*
+ * Ensure that the FDT does not overwrite the kernel or overlap
+ * its page, for the FDT to start at a page boundary.
+ */
+ const size_t fdt_max_size =
+ mem_size - align_up(kernel_size, PAGE_SIZE);
+
+ size_t fdt_allocated_size;
+
+ if (!load_secondary_fdt(stage1_locked, mem_end, fdt_max_size,
+ manifest_vm, cpio, ppool, &fdt_addr,
+ &fdt_allocated_size)) {
+ dlog_error("Unable to load FDT.\n");
+ return false;
+ }
+
+ if (!fdt_patch_mem(stage1_locked, fdt_addr, fdt_allocated_size,
+ mem_begin, mem_end, ppool)) {
+ dlog_error("Unable to patch FDT.\n");
+ return false;
+ }
+ }
+
if (!vm_init_next(manifest_vm->secondary.vcpu_count, ppool, &vm)) {
dlog_error("Unable to initialise VM.\n");
return false;
@@ -292,8 +388,19 @@
}
vcpu = vm_get_vcpu(vm, 0);
- vcpu_secondary_reset_and_start(vcpu, secondary_entry,
- pa_difference(mem_begin, mem_end));
+
+ if (has_fdt) {
+ vcpu_secondary_reset_and_start(vcpu, secondary_entry,
+ pa_addr(fdt_addr));
+ } else {
+ /*
+ * Without an FDT, secondary VMs expect the memory size to be
+ * passed in register x0, which is what
+ * vcpu_secondary_reset_and_start does in this case.
+ */
+ vcpu_secondary_reset_and_start(vcpu, secondary_entry, mem_size);
+ }
+
ret = true;
out:
diff --git a/src/manifest.c b/src/manifest.c
index 5b586f1..003a1b9 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -265,6 +265,8 @@
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));
+ TRY(read_optional_string(node, "fdt_filename",
+ &vm->secondary.fdt_filename));
}
return MANIFEST_SUCCESS;