feat: restrict `FFA_VERSION` calls

FF-A v1.2 restricts the use of the `FFA_VERSION` ABI. The FF-A version
can only be set by calls to `FFA_VERSION` before calls to any other
ABIs.

BREAKING CHANGE: Attempts to change the FF-A version after calls to
other FF-A ABIs will fail. Calls to `FFA_VERSION` that do not change the
version are still allowed.

Some tests are now failing because of this breaking change. They will be
fixed in future commits.

Change-Id: I3e24ff9c5f3eb6743c6373c97f378655fc8acbad
Signed-off-by: Karl Meakin <karl.meakin@arm.com>
diff --git a/src/api.c b/src/api.c
index 0794a79..f2118d4 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2439,13 +2439,23 @@
 	return ret;
 }
 
-/** Returns the version of the implemented FF-A specification. */
+/**
+ * Negotiate the FF-A version to be used for this FF-A instance.
+ * See section 13.2 of the FF-A v1.2 ALP1 spec.
+ *
+ * Returns Hafnium's version number (`FFA_VERSION_COMPILED`) on success.
+ * Returns `FFA_NOT_SUPPORTED` on error:
+ * - The version is invalid (highest bit set).
+ * - The requested version is incompatible.
+ * - The version has already been negotiated and cannot be changed.
+ */
 struct ffa_value api_ffa_version(struct vcpu *current,
 				 enum ffa_version requested_version)
 {
 	static_assert(sizeof(enum ffa_version) == 4,
 		      "enum ffa_version must be 4 bytes wide");
 
+	const struct ffa_value error = {.func = (uint32_t)FFA_NOT_SUPPORTED};
 	struct vm_locked current_vm_locked;
 
 	if (!ffa_version_is_valid(requested_version)) {
@@ -2453,21 +2463,36 @@
 			"FFA_VERSION: requested version %#x is invalid "
 			"(highest bit must be zero)\n",
 			requested_version);
-		return (struct ffa_value){.func = (uint32_t)FFA_NOT_SUPPORTED};
+		return error;
 	}
 
 	if (!ffa_versions_are_compatible(requested_version,
 					 FFA_VERSION_COMPILED)) {
-		dlog_verbose("Version %x incompatible with %x\n",
-			     requested_version, FFA_VERSION_COMPILED);
-		return (struct ffa_value){.func = (uint32_t)FFA_NOT_SUPPORTED};
+		dlog_error(
+			"FFA_VERSION: requested version v%u.%u is not "
+			"compatible with v%u.%u\n",
+			ffa_version_get_major(requested_version),
+			ffa_version_get_minor(requested_version),
+			ffa_version_get_major(FFA_VERSION_COMPILED),
+			ffa_version_get_minor(FFA_VERSION_COMPILED));
+		return error;
 	}
 
 	current_vm_locked = vm_lock(current->vm);
+
+	if (current_vm_locked.vm->ffa_version_negotiated &&
+	    requested_version != current_vm_locked.vm->ffa_version) {
+		vm_unlock(&current_vm_locked);
+		dlog_error(
+			"FFA_VERSION: Cannot change FF-A version after other "
+			"FF-A calls have been made\n");
+		return error;
+	}
+
 	current_vm_locked.vm->ffa_version = requested_version;
 	vm_unlock(&current_vm_locked);
 
-	return ((struct ffa_value){.func = FFA_VERSION_COMPILED});
+	return (struct ffa_value){.func = (uint32_t)FFA_VERSION_COMPILED};
 }
 
 /**