feat: vm state transitions based on lifecycle guidance

This patch performs the necessary vm state transitions based on the
guidance provided by FF-A spec for partition lifecycle.

The atomic field `aborting` in vm structure is no more needed.

Change-Id: I8bdcbeab3858f73fc3cd89c78a9314f50394a443
Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
diff --git a/src/api.c b/src/api.c
index cf3cd18..3bc2f9c 100644
--- a/src/api.c
+++ b/src/api.c
@@ -1221,6 +1221,7 @@
 	const struct ffa_value ffa_run_abi =
 		(struct ffa_value){.func = FFA_RUN_32};
 	const struct ffa_value *ffa_run_ret = NULL;
+	enum vm_state target_vm_state;
 
 	/*
 	 * Check that the registers are available so that the vCPU can be run.
@@ -1235,6 +1236,7 @@
 
 	/* The VM needs to be locked to deliver mailbox messages. */
 	need_vm_lock = vcpu->state == VCPU_STATE_WAITING ||
+		       vcpu->state == VCPU_STATE_CREATED ||
 		       (!vcpu->vm->el0_partition &&
 			(vcpu->state == VCPU_STATE_BLOCKED_INTERRUPT ||
 			 vcpu->state == VCPU_STATE_BLOCKED ||
@@ -1251,6 +1253,41 @@
 		vcpu_next_locked = vcpus_locked.vcpu2;
 	}
 
+	target_vm_state = vm_read_state(vcpu->vm);
+
+	switch (target_vm_state) {
+	case VM_STATE_NULL:
+		*run_ret = ffa_error(FFA_INVALID_PARAMETERS);
+		ret = false;
+		goto out;
+	case VM_STATE_CREATED:
+		/*
+		 * An initial FFA_RUN is necessary for vCPUs of secondary VMs
+		 * to reach the message wait loop. Note that vCPU(s) of Secure
+		 * Partitions don't need it.
+		 */
+		if (ffa_is_vm_id(vcpu->vm->id)) {
+			break;
+		}
+		*run_ret = ffa_error(FFA_BUSY);
+		ret = false;
+		goto out;
+	case VM_STATE_ABORTING:
+		if (vcpu->state != VCPU_STATE_NULL &&
+		    vcpu->state != VCPU_STATE_STOPPED &&
+		    vcpu->state != VCPU_STATE_ABORTED) {
+			dlog_verbose("VM %#x was aborted, cannot run vCPU %u\n",
+				     vcpu->vm->id, vcpu_index(vcpu));
+			vcpu->state = VCPU_STATE_ABORTED;
+		}
+		[[fallthrough]];
+	case VM_STATE_RUNNING:
+		[[fallthrough]];
+	default:
+		/* Let the subsequent checks handle further conditions. */
+		break;
+	}
+
 	/*
 	 * If the vCPU is already running somewhere then we can't run it here
 	 * simultaneously. While it is actually running then the state should be
@@ -1276,16 +1313,6 @@
 		goto out;
 	}
 
-	if (atomic_load_explicit(&vcpu->vm->aborting, memory_order_relaxed)) {
-		if (vcpu->state != VCPU_STATE_NULL &&
-		    vcpu->state != VCPU_STATE_STOPPED &&
-		    vcpu->state != VCPU_STATE_ABORTED) {
-			dlog_verbose("VM %#x was aborted, cannot run vCPU %u\n",
-				     vcpu->vm->id, vcpu_index(vcpu));
-			vcpu->state = VCPU_STATE_ABORTED;
-		}
-	}
-
 	switch (vcpu->state) {
 	case VCPU_STATE_ABORTED:
 		if (vcpu->vm->lifecycle_support) {
@@ -1315,12 +1342,17 @@
 		 * Partitions don't need it.
 		 */
 		if (ffa_is_vm_id(vcpu->vm->id)) {
+			size_t cpu_indx = cpu_index(current->cpu);
+
 			assert(vcpu->rt_model == RTM_SP_INIT);
 			vcpu->rt_model = RTM_NONE;
 
 			vcpu_was_init_state = true;
 			CHECK(vcpu_state_set(vcpu_next_locked,
 					     VCPU_STATE_STARTING));
+			if (cpu_indx == PRIMARY_CPU_IDX) {
+				vm_set_state(vm_locked, VM_STATE_RUNNING);
+			}
 			break;
 		}
 		*run_ret = ffa_error(FFA_BUSY);
@@ -2940,6 +2972,33 @@
 	current_locked = vcpus_locked.vcpu1;
 	receiver_vcpu_locked = vcpus_locked.vcpu2;
 
+	switch (vm_read_state(receiver_vm)) {
+	case VM_STATE_NULL:
+		ret = ffa_error(FFA_INVALID_PARAMETERS);
+		goto out;
+	case VM_STATE_CREATED:
+		ret = ffa_error(FFA_BUSY);
+		goto out;
+	case VM_STATE_ABORTING:
+		if (receiver_vcpu->state != VCPU_STATE_NULL &&
+		    receiver_vcpu->state != VCPU_STATE_ABORTED &&
+		    receiver_vcpu->state != VCPU_STATE_STOPPED) {
+			dlog_verbose(
+				"Receiver VM %#x aborted, cannot run vCPU %u\n",
+				receiver_vcpu->vm->id,
+				vcpu_index(receiver_vcpu));
+			CHECK(vcpu_state_set(receiver_vcpu_locked,
+					     VCPU_STATE_ABORTED));
+		}
+		/* Let the subsequent checks handle further conditions. */
+		break;
+	case VM_STATE_RUNNING:
+		[[fallthrough]];
+	default:
+		/* Let the subsequent checks handle further conditions. */
+		break;
+	}
+
 	/*
 	 * If destination vCPU is executing or already received an
 	 * FFA_MSG_SEND_DIRECT_REQ then return to caller hinting recipient is
@@ -2962,20 +3021,6 @@
 		goto out;
 	}
 
-	if (atomic_load_explicit(&receiver_vcpu->vm->aborting,
-				 memory_order_relaxed)) {
-		if (receiver_vcpu->state != VCPU_STATE_NULL &&
-		    receiver_vcpu->state != VCPU_STATE_ABORTED &&
-		    receiver_vcpu->state != VCPU_STATE_STOPPED) {
-			dlog_verbose(
-				"Receiver VM %#x aborted, cannot run vCPU %u\n",
-				receiver_vcpu->vm->id,
-				vcpu_index(receiver_vcpu));
-			CHECK(vcpu_state_set(receiver_vcpu_locked,
-					     VCPU_STATE_ABORTED));
-		}
-	}
-
 	switch (receiver_vcpu->state) {
 	case VCPU_STATE_ABORTED:
 		if (receiver_vcpu->vm->lifecycle_support) {
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index ed950dd..bf5155a 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -1128,8 +1128,7 @@
 
 	vm_locked = vm_lock(current->vm);
 
-	atomic_store_explicit(&current->vm->aborting, true,
-			      memory_order_relaxed);
+	CHECK(vm_set_state(vm_locked, VM_STATE_ABORTING));
 	current_locked = vcpu_lock(current);
 
 	/*
diff --git a/src/arch/aarch64/plat/psci/spmc.c b/src/arch/aarch64/plat/psci/spmc.c
index 56a583f..11a5b7b 100644
--- a/src/arch/aarch64/plat/psci/spmc.c
+++ b/src/arch/aarch64/plat/psci/spmc.c
@@ -158,6 +158,14 @@
 	vcpu_set_phys_core_idx(boot_vcpu);
 
 	if (cpu_index(c) == PRIMARY_CPU_IDX) {
+		struct vm_locked vm_locked;
+
+		vcpu_unlock(&vcpu_locked);
+		vm_locked = vm_lock(boot_vm);
+		vcpu_locked = vcpu_lock(boot_vcpu);
+		vm_set_state(vm_locked, VM_STATE_RUNNING);
+		vm_unlock(&vm_locked);
+
 		/*
 		 * Boot information is passed by the SPMC to the SP's execution
 		 * context only on the primary CPU.
diff --git a/src/ffa/spmc/cpu_cycles.c b/src/ffa/spmc/cpu_cycles.c
index 96aa1aa..d9cab9c 100644
--- a/src/ffa/spmc/cpu_cycles.c
+++ b/src/ffa/spmc/cpu_cycles.c
@@ -290,8 +290,7 @@
 		return false;
 	}
 
-	if (!atomic_load_explicit(&current->vm->aborting,
-				  memory_order_relaxed)) {
+	if (vm_read_state(current->vm) != VM_STATE_ABORTING) {
 		/* vCPU has just returned from successful initialization. */
 		dlog_verbose(
 			"Initialized execution context of VM: %#x on CPU: %zu, "
@@ -300,7 +299,7 @@
 			current->vm->boot_order);
 	}
 
-	if (cpu_index(current_locked.vcpu->cpu) == PRIMARY_CPU_IDX) {
+	if (cpu_indx == PRIMARY_CPU_IDX) {
 		next_vm = vm_get_next_boot(current->vm);
 	} else {
 		/* SP boot chain on secondary CPU. */
@@ -331,8 +330,18 @@
 	if (vcpu_next->rt_model == RTM_SP_INIT ||
 	    vcpu_next->state == VCPU_STATE_OFF) {
 		struct vcpu_locked vcpu_next_locked;
+		struct vm_locked vm_locked;
 
+		vm_locked = vm_lock(next_vm);
 		vcpu_next_locked = vcpu_lock(vcpu_next);
+
+		if (cpu_indx == PRIMARY_CPU_IDX &&
+		    vcpu_next->state == VCPU_STATE_CREATED) {
+			vm_set_state(vm_locked, VM_STATE_RUNNING);
+		}
+
+		vm_unlock(&vm_locked);
+
 		vcpu_next->rt_model = RTM_SP_INIT;
 		arch_regs_reset(vcpu_next);
 		vcpu_next->cpu = current->cpu;
@@ -849,9 +858,7 @@
 		dlog_error("Aborting SP %#x from vCPU %u\n", current->vm->id,
 			   vcpu_index(current));
 
-		atomic_store_explicit(&current->vm->aborting, true,
-				      memory_order_relaxed);
-
+		CHECK(vm_set_state(vm_locked, VM_STATE_ABORTING));
 		ffa_vm_free_resources(vm_locked);
 
 		if (sp_boot_next(current_locked, next)) {
diff --git a/src/ffa/spmc/interrupts.c b/src/ffa/spmc/interrupts.c
index da3b9fc..0be9b59 100644
--- a/src/ffa/spmc/interrupts.c
+++ b/src/ffa/spmc/interrupts.c
@@ -463,8 +463,7 @@
 	 * execution context is resumed.
 	 */
 	if (target_vcpu->state == VCPU_STATE_ABORTED ||
-	    atomic_load_explicit(&target_vcpu->vm->aborting,
-				 memory_order_relaxed)) {
+	    vm_read_state(target_vcpu->vm) == VM_STATE_ABORTING) {
 		/* Clear fields corresponding to secure interrupt handling. */
 		vcpu_secure_interrupt_complete(target_vcpu_locked);
 		ffa_vm_disable_interrupts(target_vm_locked);
diff --git a/src/load.c b/src/load.c
index 219c99b..2e70ebc 100644
--- a/src/load.c
+++ b/src/load.c
@@ -420,6 +420,7 @@
 	vcpu_prepare(vcpu_locked, primary_entry, params->kernel_arg);
 	vcpu_unlock(&vcpu_locked);
 	ret = true;
+	CHECK(vm_set_state(vm_locked, VM_STATE_CREATED));
 
 out:
 	vm_unlock(&vm_locked);
@@ -853,6 +854,7 @@
 	}
 
 	ret = true;
+	CHECK(vm_set_state(vm_locked, VM_STATE_CREATED));
 
 out:
 	vm_unlock(&vm_locked);
diff --git a/src/vm.c b/src/vm.c
index e3039d7..857f10e 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -86,7 +86,6 @@
 	CHECK(vm->vcpus != NULL);
 
 	vm->mailbox.state = MAILBOX_STATE_EMPTY;
-	atomic_init(&vm->aborting, false);
 	vm->el0_partition = el0_partition;
 	vm->dma_device_count = dma_device_count;