manifest: Require 'compatible' property in 'hypervisor' node
Other hypervisors also use a 'hypervisor' node in the FDT to pass
config information to the VMs. Add requirement to specify which
hypervisors the node is compatible with. The 'compatible' property
is a list of NULL-separated strings in the format
"<manufacturer>,<model>". Following the naming convention of other
projects, we will use "hafnium,hafnium" as a match-all-versions value,
and later add "hafnium,hafnium_<version>" values to match specific
releases of Hafnium.
Bug: 117551352
Change-Id: Ie6dadcdace37318d4d122e80fefe989715ee9cc9
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index 37f26f3..dd7a122 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -47,11 +47,13 @@
MANIFEST_ERROR_CORRUPTED_FDT,
MANIFEST_ERROR_NO_ROOT_FDT_NODE,
MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE,
+ MANIFEST_ERROR_NOT_COMPATIBLE,
MANIFEST_ERROR_RESERVED_VM_ID,
MANIFEST_ERROR_NO_PRIMARY_VM,
MANIFEST_ERROR_TOO_MANY_VMS,
MANIFEST_ERROR_PROPERTY_NOT_FOUND,
MANIFEST_ERROR_MALFORMED_STRING,
+ MANIFEST_ERROR_MALFORMED_STRING_LIST,
MANIFEST_ERROR_MALFORMED_INTEGER,
MANIFEST_ERROR_INTEGER_OVERFLOW,
};
diff --git a/inc/hf/memiter.h b/inc/hf/memiter.h
index a44ffef..f8e8c25 100644
--- a/inc/hf/memiter.h
+++ b/inc/hf/memiter.h
@@ -32,5 +32,5 @@
void memiter_dlog_str(struct memiter *it);
bool memiter_advance(struct memiter *it, size_t v);
-const void *memiter_base(struct memiter *it);
-size_t memiter_size(struct memiter *it);
+const void *memiter_base(const struct memiter *it);
+size_t memiter_size(const struct memiter *it);
diff --git a/inc/hf/std.h b/inc/hf/std.h
index 1c4458c..9c6b494 100644
--- a/inc/hf/std.h
+++ b/inc/hf/std.h
@@ -38,4 +38,6 @@
void memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
void memmove_s(void *dest, rsize_t destsz, const void *src, rsize_t count);
+void *memchr(const void *ptr, int ch, size_t count);
+
size_t strnlen_s(const char *str, size_t strsz);
diff --git a/src/manifest.c b/src/manifest.c
index e5c62a1..4cf490a 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -64,7 +64,11 @@
return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
}
- if (data[size - 1] != '\0') {
+ /*
+ * Require that the value contains exactly one NULL character and that
+ * it is the last byte.
+ */
+ if (memchr(data, '\0', size) != &data[size - 1]) {
return MANIFEST_ERROR_MALFORMED_STRING;
}
@@ -106,6 +110,87 @@
return MANIFEST_SUCCESS;
}
+/**
+ * Represents the value of property whose type is a list of strings. These are
+ * encoded as one contiguous byte buffer with NULL-separated entries.
+ */
+struct stringlist_iter {
+ struct memiter mem_it;
+};
+
+static enum manifest_return_code read_stringlist(const struct fdt_node *node,
+ const char *property,
+ struct stringlist_iter *out)
+{
+ const char *data;
+ uint32_t size;
+
+ if (!fdt_read_property(node, property, &data, &size)) {
+ return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
+ }
+
+ /*
+ * Require that the value ends with a NULL terminator. Other NULL
+ * characters separate the string list entries.
+ */
+ if (data[size - 1] != '\0') {
+ return MANIFEST_ERROR_MALFORMED_STRING_LIST;
+ }
+
+ memiter_init(&out->mem_it, data, size - 1);
+ return MANIFEST_SUCCESS;
+}
+
+static bool stringlist_has_next(const struct stringlist_iter *list)
+{
+ return memiter_size(&list->mem_it) > 0;
+}
+
+static void stringlist_get_next(struct stringlist_iter *list,
+ struct memiter *out)
+{
+ const char *mem_base = memiter_base(&list->mem_it);
+ size_t mem_size = memiter_size(&list->mem_it);
+ const char *null_term;
+
+ CHECK(stringlist_has_next(list));
+
+ null_term = memchr(mem_base, '\0', mem_size);
+ if (null_term == NULL) {
+ /*
+ * NULL terminator not found, this is the last entry.
+ * Set entry memiter to the entire byte range and advance list
+ * memiter to the end of the byte range.
+ */
+ memiter_init(out, mem_base, mem_size);
+ memiter_advance(&list->mem_it, mem_size);
+ } else {
+ /*
+ * Found NULL terminator. Set entry memiter to byte range
+ * [base, null) and move list memiter past the terminator.
+ */
+ size_t entry_size = null_term - mem_base;
+
+ memiter_init(out, mem_base, entry_size);
+ memiter_advance(&list->mem_it, entry_size + 1);
+ }
+}
+
+static bool stringlist_contains(const struct stringlist_iter *list,
+ const char *str)
+{
+ struct stringlist_iter it = *list;
+ struct memiter entry;
+
+ while (stringlist_has_next(&it)) {
+ stringlist_get_next(&it, &entry);
+ if (memiter_iseq(&entry, str)) {
+ return true;
+ }
+ }
+ return false;
+}
+
static enum manifest_return_code parse_vm(struct fdt_node *node,
struct manifest_vm *vm,
spci_vm_id_t vm_id)
@@ -128,6 +213,7 @@
{
char vm_name_buf[VM_NAME_BUF_SIZE];
struct fdt_node hyp_node;
+ struct stringlist_iter compatible_list;
size_t i = 0;
bool found_primary_vm = false;
@@ -145,6 +231,12 @@
return MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE;
}
+ /* Check "compatible" property. */
+ TRY(read_stringlist(&hyp_node, "compatible", &compatible_list));
+ if (!stringlist_contains(&compatible_list, "hafnium,hafnium")) {
+ return MANIFEST_ERROR_NOT_COMPATIBLE;
+ }
+
/* 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;
@@ -197,6 +289,8 @@
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_NOT_COMPATIBLE:
+ return "Hypervisor manifest entry not compatible with Hafnium";
case MANIFEST_ERROR_RESERVED_VM_ID:
return "Manifest defines a VM with a reserved ID";
case MANIFEST_ERROR_NO_PRIMARY_VM:
@@ -208,6 +302,8 @@
return "Property not found";
case MANIFEST_ERROR_MALFORMED_STRING:
return "Malformed string property";
+ case MANIFEST_ERROR_MALFORMED_STRING_LIST:
+ return "Malformed string list property";
case MANIFEST_ERROR_MALFORMED_INTEGER:
return "Malformed integer property";
case MANIFEST_ERROR_INTEGER_OVERFLOW:
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index 0a23745..432b8f8 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -144,6 +144,12 @@
return *this;
}
+ ManifestDtBuilder &Compatible(const std::vector<std::string_view>
+ &value = {"hafnium,hafnium"})
+ {
+ return StringListProperty("compatible", value);
+ }
+
ManifestDtBuilder &DebugName(const std::string_view &value)
{
return StringProperty("debug_name", value);
@@ -172,6 +178,25 @@
return *this;
}
+ ManifestDtBuilder &StringListProperty(
+ const std::string_view &name,
+ const std::vector<std::string_view> &value)
+ {
+ bool is_first = true;
+
+ dts_ << name << " = ";
+ for (const std::string_view &entry : value) {
+ if (is_first) {
+ is_first = false;
+ } else {
+ dts_ << ", ";
+ }
+ dts_ << "\"" << entry << "\"";
+ }
+ dts_ << ";" << std::endl;
+ return *this;
+ }
+
ManifestDtBuilder &IntegerProperty(const std::string_view &name,
uint64_t value)
{
@@ -193,7 +218,7 @@
MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE);
}
-TEST(manifest, no_vms)
+TEST(manifest, no_compatible_property)
{
struct manifest m;
struct memiter it;
@@ -206,10 +231,10 @@
/* clang-format on */
memiter_init(&it, dtb.data(), dtb.size());
- ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_NO_PRIMARY_VM);
+ ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_PROPERTY_NOT_FOUND);
}
-TEST(manifest, reserved_vmid)
+TEST(manifest, not_compatible)
{
struct manifest m;
struct memiter it;
@@ -217,6 +242,61 @@
/* clang-format off */
std::vector<char> dtb = ManifestDtBuilder()
.StartChild("hypervisor")
+ .Compatible({ "foo,bar" })
+ .EndChild()
+ .Build();
+ /* clang-format on */
+
+ memiter_init(&it, dtb.data(), dtb.size());
+ ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_NOT_COMPATIBLE);
+}
+
+TEST(manifest, compatible_one_of_many)
+{
+ struct manifest m;
+ struct memiter it;
+
+ /* clang-format off */
+ std::vector<char> dtb = ManifestDtBuilder()
+ .StartChild("hypervisor")
+ .Compatible({ "foo,bar", "hafnium,hafnium" })
+ .StartChild("vm1")
+ .DebugName("primary")
+ .EndChild()
+ .EndChild()
+ .Build();
+ /* clang-format on */
+
+ memiter_init(&it, dtb.data(), dtb.size());
+ ASSERT_EQ(manifest_init(&m, &it), MANIFEST_SUCCESS);
+}
+
+TEST(manifest, no_vm_nodes)
+{
+ struct manifest m;
+ struct memiter it;
+
+ /* clang-format off */
+ std::vector<char> dtb = ManifestDtBuilder()
+ .StartChild("hypervisor")
+ .Compatible()
+ .EndChild()
+ .Build();
+ /* clang-format on */
+
+ memiter_init(&it, dtb.data(), dtb.size());
+ ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_NO_PRIMARY_VM);
+}
+
+TEST(manifest, reserved_vm_id)
+{
+ struct manifest m;
+ struct memiter it;
+
+ /* clang-format off */
+ std::vector<char> dtb = ManifestDtBuilder()
+ .StartChild("hypervisor")
+ .Compatible()
.StartChild("vm1")
.DebugName("primary_vm")
.EndChild()
@@ -239,6 +319,7 @@
/* clang-format off */
return ManifestDtBuilder()
.StartChild("hypervisor")
+ .Compatible()
.StartChild("vm1")
.DebugName("primary_vm")
.EndChild()
@@ -279,6 +360,7 @@
/* clang-format off */
std::vector<char> dtb = ManifestDtBuilder()
.StartChild("hypervisor")
+ .Compatible()
.StartChild("vm1")
.DebugName("primary_vm")
.EndChild()
diff --git a/src/memiter.c b/src/memiter.c
index 3dec9c6..3331d37 100644
--- a/src/memiter.c
+++ b/src/memiter.c
@@ -153,7 +153,7 @@
return true;
}
-const void *memiter_base(struct memiter *it)
+const void *memiter_base(const struct memiter *it)
{
return (const void *)it->next;
}
@@ -161,7 +161,7 @@
/**
* Returns the number of bytes in interval [it.next, it.limit).
*/
-size_t memiter_size(struct memiter *it)
+size_t memiter_size(const struct memiter *it)
{
return it->limit - it->next;
}
diff --git a/src/std.c b/src/std.c
index a467384..84806e2 100644
--- a/src/std.c
+++ b/src/std.c
@@ -16,7 +16,7 @@
#include "hf/std.h"
-#include "hf/panic.h"
+#include "hf/check.h"
/* Declare unsafe functions locally so they are not available globally. */
void *memset(void *s, int c, size_t n);
@@ -89,6 +89,30 @@
}
/**
+ * Finds the first occurrence of character `ch` in the first `count` bytes of
+ * memory pointed to by `ptr`.
+ *
+ * Returns NULL if `ch` is not found.
+ * Panics if `ptr` is NULL (undefined behaviour).
+ */
+void *memchr(const void *ptr, int ch, size_t count)
+{
+ size_t i;
+ const unsigned char *p = (const unsigned char *)ptr;
+
+ CHECK(ptr != NULL);
+
+ /* Iterate over at most `strsz` characters of `str`. */
+ for (i = 0; i < count; ++i) {
+ if (p[i] == (unsigned char)ch) {
+ return (void *)(&p[i]);
+ }
+ }
+
+ return NULL;
+}
+
+/**
* Returns the length of the null-terminated byte string `str`, examining at
* most `strsz` bytes.
*
diff --git a/test/linux/manifest.dts b/test/linux/manifest.dts
index 5437968..f863c6e 100644
--- a/test/linux/manifest.dts
+++ b/test/linux/manifest.dts
@@ -18,6 +18,7 @@
/ {
hypervisor {
+ compatible = "hafnium,hafnium";
vm1 {
debug_name = "primary";
};
diff --git a/test/vmapi/gicv3/manifest.dts b/test/vmapi/gicv3/manifest.dts
index 4e90046..da3cc13 100644
--- a/test/vmapi/gicv3/manifest.dts
+++ b/test/vmapi/gicv3/manifest.dts
@@ -18,6 +18,7 @@
/ {
hypervisor {
+ compatible = "hafnium,hafnium";
vm1 {
debug_name = "primary";
};
diff --git a/test/vmapi/primary_only/manifest.dts b/test/vmapi/primary_only/manifest.dts
index 0e1b082..11d7edf 100644
--- a/test/vmapi/primary_only/manifest.dts
+++ b/test/vmapi/primary_only/manifest.dts
@@ -18,6 +18,7 @@
/ {
hypervisor {
+ compatible = "hafnium,hafnium";
vm1 {
debug_name = "primary";
};
diff --git a/test/vmapi/primary_with_secondaries/manifest.dts b/test/vmapi/primary_with_secondaries/manifest.dts
index 741becd..ac1530e 100644
--- a/test/vmapi/primary_with_secondaries/manifest.dts
+++ b/test/vmapi/primary_with_secondaries/manifest.dts
@@ -18,6 +18,7 @@
/ {
hypervisor {
+ compatible = "hafnium,hafnium";
vm1 {
debug_name = "primary";
};