Introduce a DT-based manifest
These are first steps towards a new manifest format. A new "device_tree"
build target is introduced to compile DTS files to DTB, and
`generate_initrd.py` now does not produce a "vms.txt" file. Instead
"initrd" targets are expected to provide a path to a DTS manifest in the
format:
/dts-v1/;
/ {
hypervisor {
vm1 {
debug_name = "primary";
};
vm2 {
debug_name = "secondary1";
kernel_filename = "filename";
vcpu_count = <N>;
mem_size = <M>;
};
...
};
};
The information provided in the manifest matches "vms.txt".
Bug: 117551352
Test: manifest_test.cc
Test: used by hftest
Change-Id: I6b70bd44d2b110c4f7a6b971018c834084b6d8c4
diff --git a/src/manifest.c b/src/manifest.c
new file mode 100644
index 0000000..e5c62a1
--- /dev/null
+++ b/src/manifest.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hf/manifest.h"
+
+#include "hf/addr.h"
+#include "hf/check.h"
+#include "hf/fdt.h"
+#include "hf/static_assert.h"
+#include "hf/std.h"
+
+#define TRY(expr) \
+ do { \
+ enum manifest_return_code ret_code = (expr); \
+ if (ret_code != MANIFEST_SUCCESS) { \
+ return ret_code; \
+ } \
+ } while (0)
+
+#define VM_NAME_BUF_SIZE (2 + 5 + 1) /* "vm" + number + null terminator */
+static_assert(MAX_VMS <= 99999, "Insufficient VM_NAME_BUF_SIZE");
+
+/**
+ * Generates a string with the two letters "vm" followed by an integer.
+ * Assumes `buf` is of size VM_NAME_BUF_SIZE.
+ */
+static const char *generate_vm_node_name(char *buf, spci_vm_id_t vm_id)
+{
+ static const char *digits = "0123456789";
+ char *ptr = buf + VM_NAME_BUF_SIZE;
+
+ *(--ptr) = '\0';
+ do {
+ *(--ptr) = digits[vm_id % 10];
+ vm_id /= 10;
+ } while (vm_id);
+ *(--ptr) = 'm';
+ *(--ptr) = 'v';
+
+ return ptr;
+}
+
+static enum manifest_return_code read_string(const struct fdt_node *node,
+ const char *property,
+ struct memiter *out)
+{
+ const char *data;
+ uint32_t size;
+
+ if (!fdt_read_property(node, property, &data, &size)) {
+ return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
+ }
+
+ if (data[size - 1] != '\0') {
+ return MANIFEST_ERROR_MALFORMED_STRING;
+ }
+
+ memiter_init(out, data, size - 1);
+ return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code read_uint64(const struct fdt_node *node,
+ const char *property,
+ uint64_t *out)
+{
+ const char *data;
+ uint32_t size;
+
+ if (!fdt_read_property(node, property, &data, &size)) {
+ return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
+ }
+
+ if (!fdt_parse_number(data, size, out)) {
+ return MANIFEST_ERROR_MALFORMED_INTEGER;
+ }
+
+ return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code read_uint16(const struct fdt_node *node,
+ const char *property,
+ uint16_t *out)
+{
+ uint64_t value;
+
+ TRY(read_uint64(node, property, &value));
+
+ if (value > UINT16_MAX) {
+ return MANIFEST_ERROR_INTEGER_OVERFLOW;
+ }
+
+ *out = (uint16_t)value;
+ return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code parse_vm(struct fdt_node *node,
+ struct manifest_vm *vm,
+ spci_vm_id_t vm_id)
+{
+ TRY(read_string(node, "debug_name", &vm->debug_name));
+ if (vm_id != HF_PRIMARY_VM_ID) {
+ TRY(read_string(node, "kernel_filename",
+ &vm->secondary.kernel_filename));
+ TRY(read_uint64(node, "mem_size", &vm->secondary.mem_size));
+ TRY(read_uint16(node, "vcpu_count", &vm->secondary.vcpu_count));
+ }
+ return MANIFEST_SUCCESS;
+}
+
+/**
+ * Parse manifest from FDT.
+ */
+enum manifest_return_code manifest_init(struct manifest *manifest,
+ struct memiter *fdt)
+{
+ char vm_name_buf[VM_NAME_BUF_SIZE];
+ struct fdt_node hyp_node;
+ size_t i = 0;
+ bool found_primary_vm = false;
+
+ memset_s(manifest, sizeof(*manifest), 0, sizeof(*manifest));
+
+ /* Find hypervisor node. */
+ if (!fdt_root_node(&hyp_node,
+ (const struct fdt_header *)memiter_base(fdt))) {
+ return MANIFEST_ERROR_CORRUPTED_FDT;
+ }
+ if (!fdt_find_child(&hyp_node, "")) {
+ return MANIFEST_ERROR_NO_ROOT_FDT_NODE;
+ }
+ if (!fdt_find_child(&hyp_node, "hypervisor")) {
+ return MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE;
+ }
+
+ /* Iterate over reserved VM IDs and check no such nodes exist. */
+ for (i = 0; i < HF_VM_ID_OFFSET; i++) {
+ spci_vm_id_t vm_id = (spci_vm_id_t)i;
+ struct fdt_node vm_node = hyp_node;
+ const char *vm_name = generate_vm_node_name(vm_name_buf, vm_id);
+
+ if (fdt_find_child(&vm_node, vm_name)) {
+ return MANIFEST_ERROR_RESERVED_VM_ID;
+ }
+ }
+
+ /* Iterate over VM nodes until we find one that does not exist. */
+ for (i = 0; i <= MAX_VMS; ++i) {
+ spci_vm_id_t vm_id = HF_VM_ID_OFFSET + i;
+ struct fdt_node vm_node = hyp_node;
+ const char *vm_name = generate_vm_node_name(vm_name_buf, vm_id);
+
+ if (!fdt_find_child(&vm_node, vm_name)) {
+ break;
+ }
+
+ if (i == MAX_VMS) {
+ return MANIFEST_ERROR_TOO_MANY_VMS;
+ }
+
+ if (vm_id == HF_PRIMARY_VM_ID) {
+ CHECK(found_primary_vm == false); /* sanity check */
+ found_primary_vm = true;
+ }
+
+ manifest->num_vms = i + 1;
+ TRY(parse_vm(&vm_node, &manifest->vm[i], vm_id));
+ }
+
+ if (!found_primary_vm) {
+ return MANIFEST_ERROR_NO_PRIMARY_VM;
+ }
+
+ return MANIFEST_SUCCESS;
+}
+
+const char *manifest_strerror(enum manifest_return_code ret_code)
+{
+ switch (ret_code) {
+ case MANIFEST_SUCCESS:
+ return "Success";
+ case MANIFEST_ERROR_CORRUPTED_FDT:
+ return "Manifest failed FDT validation";
+ case MANIFEST_ERROR_NO_ROOT_FDT_NODE:
+ return "Could not find root node of manifest";
+ case MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE:
+ return "Could not find \"hypervisor\" node in manifest";
+ case MANIFEST_ERROR_RESERVED_VM_ID:
+ return "Manifest defines a VM with a reserved ID";
+ case MANIFEST_ERROR_NO_PRIMARY_VM:
+ return "Manifest does not contain a primary VM entry";
+ case MANIFEST_ERROR_TOO_MANY_VMS:
+ return "Manifest specifies more VMs than Hafnium has "
+ "statically allocated space for";
+ case MANIFEST_ERROR_PROPERTY_NOT_FOUND:
+ return "Property not found";
+ case MANIFEST_ERROR_MALFORMED_STRING:
+ return "Malformed string property";
+ case MANIFEST_ERROR_MALFORMED_INTEGER:
+ return "Malformed integer property";
+ case MANIFEST_ERROR_INTEGER_OVERFLOW:
+ return "Integer overflow";
+ }
+
+ panic("Unexpected manifest return code.");
+}