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";
 		};