feat: forward VM availability messages from SPMC to SP

* Add `plat_ffa_handle_framework_msg` and use it to check framework
  messages are valid on SPMC.
* Modify `api_ffa_dir_msg_value` to preserve the value of `args.arg2` if
  `args` is a framework message.

Change-Id: I9c66d38b9b3a4fdeb39381289418188350d7d410
Signed-off-by: Karl Meakin <karl.meakin@arm.com>
diff --git a/src/api.c b/src/api.c
index 0803a3d..4728f31 100644
--- a/src/api.c
+++ b/src/api.c
@@ -2727,7 +2727,7 @@
 	return (struct ffa_value){
 		.func = (uint32_t)args.func,
 		.arg1 = (uint32_t)args.arg1,
-		.arg2 = (uint32_t)0,
+		.arg2 = (uint32_t)args.arg2,
 		.arg3 = (uint32_t)args.arg3,
 		.arg4 = (uint32_t)args.arg4,
 		.arg5 = (uint32_t)args.arg5,
@@ -2738,27 +2738,38 @@
 
 /**
  * Helper to copy direct message payload, depending on SMC used, direct
- * messaging interface used, and expected registers size.
+ * messaging interface used, and expected registers size. Can be used for both
+ * framework messages and standard messages.
  */
 static struct ffa_value api_ffa_dir_msg_value(struct ffa_value args)
 {
-	if (args.func == FFA_MSG_SEND_DIRECT_REQ_32 ||
-	    args.func == FFA_MSG_SEND_DIRECT_RESP_32) {
-		return api_ffa_value_copy32(args);
-	}
+	switch (args.func) {
+	case FFA_MSG_SEND_DIRECT_REQ_32:
+	case FFA_MSG_SEND_DIRECT_RESP_32:
+		args = api_ffa_value_copy32(args);
+		if (!ffa_is_framework_msg(args)) {
+			args.arg2 = 0;
+		}
+		break;
 
-	if (args.func == FFA_MSG_SEND_DIRECT_REQ_64 ||
-	    args.func == FFA_MSG_SEND_DIRECT_RESP_64) {
-		args.arg2 = 0;
-	}
+	case FFA_MSG_SEND_DIRECT_REQ_64:
+	case FFA_MSG_SEND_DIRECT_RESP_64:
+		if (!ffa_is_framework_msg(args)) {
+			args.arg2 = 0;
+		}
+		break;
 
-	if (args.func == FFA_MSG_SEND_DIRECT_REQ2_64) {
+	case FFA_MSG_SEND_DIRECT_REQ2_64:
 		args.extended_val.valid = true;
-	}
+		break;
 
-	if (args.func == FFA_MSG_SEND_DIRECT_RESP2_64) {
+	case FFA_MSG_SEND_DIRECT_RESP2_64:
 		args.arg2 = 0;
 		args.arg3 = 0;
+		break;
+	default:
+		panic("Invalid direct message function %#x\n", args.func);
+		break;
 	}
 
 	return args;
@@ -2827,10 +2838,18 @@
 
 	if ((args.func == FFA_MSG_SEND_DIRECT_REQ_32 ||
 	     args.func == FFA_MSG_SEND_DIRECT_REQ_64) &&
+	    !ffa_is_framework_msg(args) &&
 	    !api_ffa_dir_msg_is_arg2_zero(args)) {
+		dlog_verbose("Direct messaging: w2 must be zero (w2 = %#lx)\n",
+			     args.arg2);
 		return ffa_error(FFA_INVALID_PARAMETERS);
 	}
 
+	if (ffa_is_framework_msg(args) &&
+	    plat_ffa_handle_framework_msg(args, &ret)) {
+		return ret;
+	}
+
 	if (!plat_ffa_is_direct_request_valid(current, sender_vm_id,
 					      receiver_vm_id)) {
 		dlog_verbose("Invalid direct message request.\n");
@@ -2838,6 +2857,7 @@
 	}
 
 	if (plat_ffa_direct_request_forward(receiver_vm_id, args, &ret)) {
+		dlog_verbose("Direct message request forwarded\n");
 		return ret;
 	}
 
@@ -2861,6 +2881,7 @@
 	 */
 	if (!plat_ffa_is_direct_request_supported(current->vm, receiver_vm,
 						  args.func)) {
+		dlog_verbose("Direct message request not supported\n");
 		return ffa_error(FFA_DENIED);
 	}
 
@@ -2930,8 +2951,6 @@
 	case VCPU_STATE_BLOCKED_INTERRUPT:
 	case VCPU_STATE_BLOCKED:
 	case VCPU_STATE_PREEMPTED:
-		dlog_verbose("Receiver's vCPU can't receive request (%u)!\n",
-			     vcpu_index(receiver_vcpu));
 		ret = ffa_error(FFA_BUSY);
 		goto out;
 	case VCPU_STATE_WAITING:
@@ -2958,6 +2977,8 @@
 	receiver_vcpu->direct_request_origin.is_ffa_req2 =
 		(args.func == FFA_MSG_SEND_DIRECT_REQ2_64);
 	receiver_vcpu->direct_request_origin.vm_id = sender_vm_id;
+	receiver_vcpu->direct_request_origin.is_framework =
+		ffa_is_framework_msg(args);
 
 	arch_regs_set_retval(&receiver_vcpu->regs, api_ffa_dir_msg_value(args));
 
@@ -3057,6 +3078,8 @@
 	struct ffa_value to_ret = api_ffa_dir_msg_value(args);
 	struct two_vcpu_locked vcpus_locked;
 	bool received_req2;
+	bool req_framework;
+	bool resp_framework;
 
 	/*
 	 * If using FFA_MSG_SEND_DIRECT_RESP, the caller's
@@ -3064,7 +3087,10 @@
 	 *  - x8-x17 SBZ if caller's FF-A version >= FF-A v1.2
 	 */
 	if (args.func != FFA_MSG_SEND_DIRECT_RESP2_64) {
-		if (!api_ffa_dir_msg_is_arg2_zero(args)) {
+		if (!ffa_is_framework_msg(args) &&
+		    !api_ffa_dir_msg_is_arg2_zero(args)) {
+			dlog_verbose("%s: w2 Must Be Zero",
+				     ffa_func_name(args.func));
 			return ffa_error(FFA_INVALID_PARAMETERS);
 		}
 
@@ -3105,6 +3131,19 @@
 		goto out;
 	}
 
+	req_framework = current_locked.vcpu->direct_request_origin.is_framework;
+	resp_framework = ffa_is_framework_msg(args);
+
+	if (req_framework && !resp_framework) {
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		dlog_verbose(
+			"Mismatch in framework message bit: request was a %s "
+			"message, but response is a %s message\n",
+			req_framework ? "framework" : "non-framework",
+			resp_framework ? "framework" : "non-framework");
+		goto out;
+	}
+
 	received_req2 = current->direct_request_origin.is_ffa_req2;
 
 	if (args.func != FFA_MSG_SEND_DIRECT_RESP2_64 && received_req2) {
diff --git a/src/arch/aarch64/plat/ffa/absent.c b/src/arch/aarch64/plat/ffa/absent.c
index e5209fd..66e258e 100644
--- a/src/arch/aarch64/plat/ffa/absent.c
+++ b/src/arch/aarch64/plat/ffa/absent.c
@@ -596,3 +596,11 @@
 {
 	return api_interrupt_get(current_locked);
 }
+
+bool plat_ffa_handle_framework_msg(struct ffa_value args, struct ffa_value *ret)
+{
+	(void)args;
+	(void)ret;
+
+	return false;
+}
diff --git a/src/arch/aarch64/plat/ffa/hypervisor.c b/src/arch/aarch64/plat/ffa/hypervisor.c
index bec5cf6..d59b54b 100644
--- a/src/arch/aarch64/plat/ffa/hypervisor.c
+++ b/src/arch/aarch64/plat/ffa/hypervisor.c
@@ -264,26 +264,35 @@
 				     struct ffa_value *ret)
 {
 	if (!ffa_tee_enabled) {
+		dlog_verbose("Not forwarding: ffa_tee_enabled is false\n");
 		return false;
 	}
 
 	/*
 	 * VM's requests should be forwarded to the SPMC, if receiver is an SP.
 	 */
-	if (!vm_id_is_current_world(receiver_vm_id)) {
-		dlog_verbose("%s calling SPMC %#lx %#lx %#lx %#lx %#lx\n",
-			     __func__, args.func, args.arg1, args.arg2,
-			     args.arg3, args.arg4);
-		if (args.func == FFA_MSG_SEND_DIRECT_REQ_64 ||
-		    args.func == FFA_MSG_SEND_DIRECT_REQ_32) {
-			*ret = arch_other_world_call(args);
-		} else if (args.func == FFA_MSG_SEND_DIRECT_REQ2_64) {
-			*ret = arch_other_world_call_ext(args);
-		}
-		return true;
+	if (vm_id_is_current_world(receiver_vm_id)) {
+		dlog_verbose(
+			"Not forwarding: receiver VM %#x is in the same "
+			"world\n",
+			receiver_vm_id);
+		return false;
 	}
 
-	return false;
+	switch (args.func) {
+	case FFA_MSG_SEND_DIRECT_REQ_32:
+	case FFA_MSG_SEND_DIRECT_REQ_64:
+		*ret = arch_other_world_call(args);
+		break;
+	case FFA_MSG_SEND_DIRECT_REQ2_64:
+		*ret = arch_other_world_call_ext(args);
+		break;
+	default:
+		panic("Invalid direct message function %#x\n", args.func);
+		break;
+	}
+
+	return true;
 }
 
 bool plat_ffa_rx_release_forward(struct vm_locked vm_locked,
@@ -2047,3 +2056,11 @@
 {
 	return api_interrupt_get(current_locked);
 }
+
+bool plat_ffa_handle_framework_msg(struct ffa_value args, struct ffa_value *ret)
+{
+	(void)args;
+	(void)ret;
+
+	return false;
+}
diff --git a/src/arch/aarch64/plat/ffa/spmc.c b/src/arch/aarch64/plat/ffa/spmc.c
index 6eeb82b..af22eb7 100644
--- a/src/arch/aarch64/plat/ffa/spmc.c
+++ b/src/arch/aarch64/plat/ffa/spmc.c
@@ -15,6 +15,7 @@
 #include "hf/arch/vmid_base.h"
 
 #include "hf/api.h"
+#include "hf/bits.h"
 #include "hf/check.h"
 #include "hf/dlog.h"
 #include "hf/ffa.h"
@@ -2964,3 +2965,97 @@
 
 	return api_interrupt_get(current_locked);
 }
+
+/**
+ * Check that the arguments to a VM availability message are correct.
+ * Returns `FFA_SUCCESS_32` if the arguments are correct.
+ * Returns `FFA_INVALID_PARAMETERS` if:
+ * - the receiver is not a valid VM
+ * - the receiver has not subscribed to the message type
+ */
+static struct ffa_value check_vm_availability_message(struct ffa_value args)
+{
+	struct ffa_value ret = ffa_error(FFA_INVALID_PARAMETERS);
+	enum ffa_framework_msg_func func = ffa_framework_msg_func(args);
+	ffa_id_t receiver_id = ffa_receiver(args);
+	struct vm_locked receiver = vm_find_locked(receiver_id);
+
+	if (receiver.vm == NULL) {
+		dlog_verbose(
+			"VM availability messaging: could not find SP %#x\n",
+			receiver_id);
+		return ret;
+	}
+
+	/* only valid if receiver has subscribed */
+	if (func == FFA_FRAMEWORK_MSG_VM_CREATION_REQ &&
+	    !receiver.vm->vm_availability_messages.vm_created) {
+		dlog_verbose(
+			"VM availability messaging: SP %#x is not subscribed "
+			"to VM creation messages\n",
+			receiver_id);
+		goto out;
+	}
+
+	if (func == FFA_FRAMEWORK_MSG_VM_DESTRUCTION_REQ &&
+	    !receiver.vm->vm_availability_messages.vm_destroyed) {
+		dlog_verbose(
+			"VM availability messaging: SP %#x is not subscribed "
+			"to VM destruction messages\n",
+			receiver_id);
+		goto out;
+	}
+
+	if (ANY_BITS_SET(args.arg5, FFA_VM_AVAILABILITY_MESSAGE_SBZ_HI,
+			 FFA_VM_AVAILABILITY_MESSAGE_SBZ_LO)) {
+		dlog_warning(
+			"VM availability messaging: bits[%u:%u] of w5 are "
+			"reserved and should be zero (w5=%#lx)\n",
+			FFA_VM_AVAILABILITY_MESSAGE_SBZ_HI,
+			FFA_VM_AVAILABILITY_MESSAGE_SBZ_LO, args.arg5);
+	}
+
+	if (args.arg6 != 0) {
+		dlog_warning(
+			"VM availability messaging: w6 is reserved and should "
+			"be zero (w6=%#lx)\n",
+			args.arg6);
+	}
+
+	if (args.arg7 != 0) {
+		dlog_warning(
+			"VM availability messaging: w7 is reserved and should "
+			"be zero (w7=%#lx)\n",
+			args.arg7);
+	}
+
+	ret = (struct ffa_value){.func = FFA_SUCCESS_32};
+
+out:
+
+	vm_unlock(&receiver);
+	return ret;
+}
+
+/**
+ * Handle framework messages: in particular, check VM availability messages are
+ * valid.
+ */
+bool plat_ffa_handle_framework_msg(struct ffa_value args, struct ffa_value *ret)
+{
+	enum ffa_framework_msg_func func = ffa_framework_msg_func(args);
+
+	switch (func) {
+	case FFA_FRAMEWORK_MSG_VM_CREATION_REQ:
+	case FFA_FRAMEWORK_MSG_VM_DESTRUCTION_REQ:
+		*ret = check_vm_availability_message(args);
+		if (ret->func != FFA_SUCCESS_32) {
+			return true;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return false;
+}
diff --git a/src/arch/fake/hypervisor/ffa.c b/src/arch/fake/hypervisor/ffa.c
index ce9e72f..2399351 100644
--- a/src/arch/fake/hypervisor/ffa.c
+++ b/src/arch/fake/hypervisor/ffa.c
@@ -675,3 +675,11 @@
 
 	return 0;
 }
+
+bool plat_ffa_handle_framework_msg(struct ffa_value args, struct ffa_value *ret)
+{
+	(void)args;
+	(void)ret;
+
+	return false;
+}