feat: perform checks for partition runtime model

Perform state transition checks across invocations of various
FF-A ABIs based on the partition runtime model.

Partition manager must return DENIED as the error code if an
invalid transition is attempted by an endpoint execution context.

Change-Id: I7bb9c23f7c50288c8ccfcc9c8928045c0168276f
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/src/api.c b/src/api.c
index 8ae4e4a..ceb7934 100644
--- a/src/api.c
+++ b/src/api.c
@@ -270,7 +270,8 @@
 {
 	struct ffa_value ret = (struct ffa_value){.func = FFA_SUCCESS_32};
 	struct vcpu_locked current_locked;
-	bool is_direct_request_ongoing;
+	bool transition_allowed;
+	enum vcpu_state next_state = VCPU_STATE_BLOCKED;
 
 	if (current->vm->id == HF_PRIMARY_VM_ID) {
 		/* NOOP on the primary as it makes the scheduling decisions. */
@@ -278,20 +279,23 @@
 	}
 
 	current_locked = vcpu_lock(current);
-	is_direct_request_ongoing =
-		is_ffa_direct_msg_request_ongoing(current_locked);
+	transition_allowed = plat_ffa_check_runtime_state_transition(
+		current, current->vm->id, HF_INVALID_VM_ID, NULL, FFA_YIELD_32,
+		&next_state);
 	vcpu_unlock(&current_locked);
 
-	if (is_direct_request_ongoing) {
+	if (!transition_allowed) {
 		return ffa_error(FFA_DENIED);
 	}
 
+	assert(next_state == VCPU_STATE_BLOCKED);
+
 	*next = api_switch_to_primary(
 		current,
 		(struct ffa_value){.func = FFA_YIELD_32,
 				   .arg1 = ffa_vm_vcpu(current->vm->id,
 						       vcpu_index(current))},
-		VCPU_STATE_BLOCKED);
+		next_state);
 
 	return ret;
 }
@@ -689,6 +693,7 @@
 				  struct ffa_value *args)
 {
 	struct ffa_value ret;
+	enum vcpu_state next_state = VCPU_STATE_WAITING;
 
 	if (args->arg1 != 0U || args->arg2 != 0U || args->arg3 != 0U ||
 	    args->arg4 != 0U || args->arg5 != 0U || args->arg6 != 0U ||
@@ -696,6 +701,14 @@
 		return ffa_error(FFA_INVALID_PARAMETERS);
 	}
 
+	if (!plat_ffa_check_runtime_state_transition(
+		    current, current->vm->id, HF_INVALID_VM_ID, NULL,
+		    FFA_MSG_WAIT_32, &next_state)) {
+		return ffa_error(FFA_DENIED);
+	}
+
+	assert(next_state == VCPU_STATE_WAITING);
+
 	if (plat_ffa_msg_wait_prepare(current, next, &ret)) {
 		return ret;
 	}
@@ -941,6 +954,8 @@
 	struct vm *vm;
 	struct vcpu *vcpu;
 	struct ffa_value ret = ffa_error(FFA_INVALID_PARAMETERS);
+	enum vcpu_state next_state = VCPU_STATE_BLOCKED;
+	struct vcpu_locked current_locked;
 
 	if (!plat_ffa_run_checks(current, vm_id, vcpu_idx, &ret, next)) {
 		return ret;
@@ -961,8 +976,23 @@
 		goto out;
 	}
 
-	/* Update state if allowed. */
-	vcpu = vm_get_vcpu(vm, vcpu_idx);
+	/*
+	 * Refer Figure 8.13 Scenario 1 of the FF-A v1.1 EAC spec. SPMC
+	 * bypasses the intermediate execution contexts and resumes the
+	 * SP execution context that was originally preempted.
+	 */
+	if (*next != NULL) {
+		vcpu = *next;
+	} else {
+		vcpu = vm_get_vcpu(vm, vcpu_idx);
+	}
+
+	if (!plat_ffa_check_runtime_state_transition(current, current->vm->id,
+						     HF_INVALID_VM_ID, vcpu,
+						     FFA_RUN_32, &next_state)) {
+		return ffa_error(FFA_DENIED);
+	}
+
 	if (!api_vcpu_prepare_run(current, vcpu, &ret)) {
 		goto out;
 	}
@@ -991,6 +1021,11 @@
 	/* Switch to the vCPU. */
 	*next = vcpu;
 
+	assert(next_state == VCPU_STATE_BLOCKED);
+	current_locked = vcpu_lock(current);
+	current->state = VCPU_STATE_BLOCKED;
+	vcpu_unlock(&current_locked);
+
 	/*
 	 * Set a placeholder return code to the scheduler. This will be
 	 * overwritten when the switch back to the primary occurs.
@@ -2445,6 +2480,7 @@
 	struct vm_locked receiver_locked;
 	struct vcpu *receiver_vcpu;
 	struct two_vcpu_locked vcpus_locked;
+	enum vcpu_state next_state = VCPU_STATE_BLOCKED;
 
 	if (!api_ffa_dir_msg_is_arg2_zero(args)) {
 		return ffa_error(FFA_INVALID_PARAMETERS);
@@ -2487,6 +2523,12 @@
 		return ffa_error(FFA_INVALID_PARAMETERS);
 	}
 
+	if (!plat_ffa_check_runtime_state_transition(
+		    current, sender_vm_id, HF_INVALID_VM_ID, receiver_vcpu,
+		    args.func, &next_state)) {
+		return ffa_error(FFA_DENIED);
+	}
+
 	receiver_locked = vm_lock(receiver_vm);
 	vcpus_locked = vcpu_lock_both(receiver_vcpu, current);
 
@@ -2554,6 +2596,7 @@
 
 	arch_regs_set_retval(&receiver_vcpu->regs, api_ffa_dir_msg_value(args));
 
+	assert(next_state == VCPU_STATE_BLOCKED);
 	current->state = VCPU_STATE_BLOCKED;
 
 	/* Switch to receiver vCPU targeted to by direct msg request */
@@ -2596,6 +2639,7 @@
 					      struct vcpu **next)
 {
 	struct vcpu_locked current_locked;
+	enum vcpu_state next_state = VCPU_STATE_WAITING;
 
 	if (!api_ffa_dir_msg_is_arg2_zero(args)) {
 		return ffa_error(FFA_INVALID_PARAMETERS);
@@ -2608,6 +2652,13 @@
 		return ffa_error(FFA_INVALID_PARAMETERS);
 	}
 
+	if (!plat_ffa_check_runtime_state_transition(current, sender_vm_id,
+						     receiver_vm_id, NULL,
+						     args.func, &next_state)) {
+		return ffa_error(FFA_DENIED);
+	}
+
+	assert(next_state == VCPU_STATE_WAITING);
 	current_locked = vcpu_lock(current);
 	if (api_ffa_is_managed_exit_ongoing(current_locked)) {
 		/*