feat: implement state machine for vCPU state transitions

This patch implements the state machine which tracks the various
legal transitions allowed for a vCPU as defined by the rules in
the FF-A v1.3 ALP2 specification.

Change-Id: I03f2d398b1ffc965e69449277536403605267766
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/src/vcpu.c b/src/vcpu.c
index ba52d3b..efefc9c 100644
--- a/src/vcpu.c
+++ b/src/vcpu.c
@@ -84,7 +84,7 @@
 void vcpu_prepare(struct vcpu_locked vcpu, ipaddr_t entry, uintreg_t arg)
 {
 	arch_regs_set_pc_arg(&vcpu.vcpu->regs, entry, arg);
-	vcpu.vcpu->state = VCPU_STATE_CREATED;
+	CHECK(vcpu_state_set(vcpu, VCPU_STATE_CREATED));
 }
 
 ffa_vcpu_index_t vcpu_index(const struct vcpu *vcpu)
@@ -687,3 +687,146 @@
 	vcpu->scheduling_mode = NONE;
 	vcpu->rt_model = RTM_NONE;
 }
+
+static inline const char *vcpu_state_print_name(enum vcpu_state state)
+{
+	switch (state) {
+	case VCPU_STATE_OFF:
+		return "VCPU_STATE_OFF";
+	case VCPU_STATE_RUNNING:
+		return "VCPU_STATE_RUNNING";
+	case VCPU_STATE_WAITING:
+		return "VCPU_STATE_WAITING";
+	case VCPU_STATE_BLOCKED:
+		return "VCPU_STATE_BLOCKED";
+	case VCPU_STATE_PREEMPTED:
+		return "VCPU_STATE_PREEMPTED";
+	case VCPU_STATE_BLOCKED_INTERRUPT:
+		return "VCPU_STATE_BLOCKED_INTERRUPT";
+	case VCPU_STATE_ABORTED:
+		return "VCPU_STATE_ABORTED";
+	case VCPU_STATE_NULL:
+		return "VCPU_STATE_NULL";
+	case VCPU_STATE_STOPPED:
+		return "VCPU_STATE_STOPPED";
+	case VCPU_STATE_CREATED:
+		return "VCPU_STATE_CREATED";
+	case VCPU_STATE_STARTING:
+		return "VCPU_STATE_STARTING";
+	case VCPU_STATE_STOPPING:
+		return "VCPU_STATE_STOPPING";
+	}
+}
+
+static bool vcpu_is_valid_transition_from_starting_running_state(
+	enum vcpu_state to_state)
+{
+	switch (to_state) {
+	case VCPU_STATE_WAITING:
+	case VCPU_STATE_BLOCKED:
+	case VCPU_STATE_PREEMPTED:
+	case VCPU_STATE_ABORTED:
+	case VCPU_STATE_STOPPING:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool vcpu_is_valid_transition_from_stopping_state(
+	enum vcpu_state to_state)
+{
+	switch (to_state) {
+	case VCPU_STATE_WAITING:
+	case VCPU_STATE_BLOCKED:
+	case VCPU_STATE_PREEMPTED:
+	case VCPU_STATE_ABORTED:
+	case VCPU_STATE_STOPPED:
+		return true;
+	default:
+		return false;
+	}
+}
+
+bool vcpu_state_set(struct vcpu_locked vcpu_locked, enum vcpu_state to_state)
+{
+	struct vcpu *vcpu = vcpu_locked.vcpu;
+	enum vcpu_state from_state = vcpu->state;
+	bool ret;
+
+	/*
+	 * SPMC can turn off a vCPU, irrespective of the present state, when it
+	 * receives CPU_OFF power management message from SPMD.
+	 */
+	if (ffa_is_vm_id(vcpu->vm->id) || from_state == to_state ||
+	    to_state == VCPU_STATE_OFF) {
+		ret = true;
+		goto out;
+	}
+
+	switch (from_state) {
+	case VCPU_STATE_OFF:
+		/*
+		 * When a CPU is powered on after previously being powered down,
+		 * the pinned vCPU of an SP moves from OFF to CREATED before
+		 * transitioning to STARTING state.
+		 */
+		ret = (to_state == VCPU_STATE_STARTING ||
+		       to_state == VCPU_STATE_CREATED);
+		break;
+	case VCPU_STATE_NULL:
+		ret = (to_state == VCPU_STATE_CREATED);
+		break;
+	case VCPU_STATE_CREATED:
+		ret = (to_state == VCPU_STATE_STARTING);
+		break;
+	case VCPU_STATE_STARTING:
+	case VCPU_STATE_RUNNING:
+		ret = vcpu_is_valid_transition_from_starting_running_state(
+			to_state);
+		break;
+	case VCPU_STATE_STOPPING:
+		ret = vcpu_is_valid_transition_from_stopping_state(to_state);
+		break;
+	case VCPU_STATE_WAITING:
+		/*
+		 * A transition of vCPU from WAITING to ABORTED is legal. One
+		 * such scenario is when the endpoint was aborted.
+		 */
+		ret = (to_state == VCPU_STATE_RUNNING ||
+		       to_state == VCPU_STATE_OFF ||
+		       to_state == VCPU_STATE_ABORTED);
+		break;
+	case VCPU_STATE_BLOCKED:
+	case VCPU_STATE_BLOCKED_INTERRUPT:
+	case VCPU_STATE_PREEMPTED:
+		ret = (to_state == VCPU_STATE_RUNNING);
+		break;
+	case VCPU_STATE_ABORTED:
+		ret = (to_state == VCPU_STATE_NULL ||
+		       to_state == VCPU_STATE_STOPPED);
+		break;
+	case VCPU_STATE_STOPPED:
+		/*
+		 * A legacy SP will be put in ABORTED state by SPMC if it
+		 * encounters a fatal error in runtime.
+		 */
+		ret = (to_state == VCPU_STATE_CREATED ||
+		       to_state == VCPU_STATE_NULL ||
+		       to_state == VCPU_STATE_ABORTED);
+		break;
+	}
+out:
+	if (!ret) {
+		dlog_error(
+			"Partition %#x vCPU %u: transition from %s to %s "
+			"failed.\n",
+			vcpu->vm->id, (uint32_t)cpu_index(vcpu->cpu),
+			vcpu_state_print_name(from_state),
+			vcpu_state_print_name(to_state));
+	} else {
+		vcpu->state = to_state;
+	}
+
+	return ret;
+}