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/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()