test: disallow migrating a blocked SP to another CPU

This patch adds a test to make sure the vCPU of an UP SP cannot be
migrated from current CPU to a different physical CPU while the vCPU
is in BLOCKED state as part of an SP call chain.

This patch also adds a helper function to check if the receiver
endpoint is an UP SP. This helper is used as a precondition for the
newly introduced test.

Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
Change-Id: I018c152737dbd9abae7385e8858a8d231d5e82e8
diff --git a/src/arch/aarch64/inc/hf/arch/vm/delay.h b/src/arch/aarch64/inc/hf/arch/vm/delay.h
new file mode 100644
index 0000000..b45564f
--- /dev/null
+++ b/src/arch/aarch64/inc/hf/arch/vm/delay.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Hafnium Authors.
+ *
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/BSD-3-Clause.
+ */
+
+#include "hf/arch/barriers.h"
+
+#include "msr.h"
+
+static inline uint64_t syscounter_read(void)
+{
+	isb();
+	return read_msr(cntvct_el0);
+}
+
+static inline void waitus(uint64_t us)
+{
+	uint64_t start_count_val = syscounter_read();
+	uint64_t wait_cycles = (us * read_msr(cntfrq_el0)) / 1000000;
+
+	while ((syscounter_read() - start_count_val) < wait_cycles) {
+		/* Busy wait... */
+	}
+}
+
+static inline void waitms(uint64_t ms)
+{
+	while (ms > 0) {
+		waitus(1000);
+		ms--;
+	}
+}
diff --git a/test/vmapi/ffa_secure_partitions/dir_msg.c b/test/vmapi/ffa_secure_partitions/dir_msg.c
index b4325c8..a106c8e 100644
--- a/test/vmapi/ffa_secure_partitions/dir_msg.c
+++ b/test/vmapi/ffa_secure_partitions/dir_msg.c
@@ -6,15 +6,29 @@
  * https://opensource.org/licenses/BSD-3-Clause.
  */
 
+#include "hf/arch/vm/delay.h"
+#include "hf/arch/vm/power_mgmt.h"
+
 #include "hf/dlog.h"
 #include "hf/ffa.h"
+#include "hf/mm.h"
 
 #include "vmapi/hf/call.h"
 
+#include "ffa_secure_partitions.h"
 #include "partition_services.h"
 #include "test/hftest.h"
 #include "test/vmapi/ffa.h"
 
+#define SP_SLEEP_LONG 2000U
+
+alignas(4096) static uint8_t secondary_ec_stack[MAX_CPUS - 1][PAGE_SIZE];
+
+struct secondary_cpu_entry_args {
+	ffa_vm_id_t receiver_id;
+	ffa_vcpu_count_t vcpu_count;
+};
+
 /**
  * Communicates with partition via direct messaging to validate functioning of
  * direct request/response interfaces.
@@ -86,3 +100,84 @@
 
 	EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
 }
+
+static void migrate_busy_up_sp(uintptr_t arg)
+{
+	ffa_vm_id_t own_id = hf_vm_get_id();
+	struct ffa_value res;
+	const uint32_t msg[] = {SP_ECHO_CMD, 0x1, 0x2, 0x3, 0x4};
+	struct secondary_cpu_entry_args *args =
+		// NOLINTNEXTLINE(performance-no-int-to-ptr)
+		(struct secondary_cpu_entry_args *)arg;
+
+	/*
+	 * Wait till the primary VM on boot CPU has established the
+	 * call chain with receiver SP.
+	 */
+	waitms(200);
+
+	/*
+	 * A direct request message cannot be serviced by the receiver SP
+	 * since it is in a BLOCKED state on a boot CPU.
+	 */
+	res = sp_echo_cmd_send(own_id, args->receiver_id, msg[0], msg[1],
+			       msg[2], msg[3]);
+
+	EXPECT_EQ(ffa_func_id(res), FFA_ERROR_32);
+
+	/*
+	 * An attempt to migrate the UP SP from boot CPU to current CPU using
+	 * FFA_RUN interface should fail.
+	 */
+	res = ffa_run(args->receiver_id, 0);
+
+	EXPECT_EQ(ffa_func_id(res), FFA_ERROR_32);
+
+	arch_cpu_stop();
+}
+
+/**
+ * Test to make sure the vCPU of an UP SP cannot be migrated from current CPU
+ * to a different physical CPU while the vCPU is in BLOCKED state as part of an
+ * SP call chain.
+ */
+TEST_PRECONDITION(ffa_call_chain, disallow_migration_blocked_sp,
+		  service2_is_up_sp)
+{
+	struct ffa_value res;
+	ffa_vm_id_t own_id = hf_vm_get_id();
+	struct secondary_cpu_entry_args args;
+	struct mailbox_buffers mb = set_up_mailbox();
+	struct ffa_partition_info *receiver_info = service2(mb.recv);
+	struct ffa_partition_info *companion_info = service1(mb.recv);
+	ffa_vm_id_t receiver_id = receiver_info->vm_id;
+	ffa_vm_id_t companion_id = companion_info->vm_id;
+
+	args.receiver_id = receiver_id;
+	args.vcpu_count = receiver_info->vcpu_count;
+
+	for (size_t i = 1; i < MAX_CPUS; i++) {
+		uintptr_t id;
+
+		id = hftest_get_cpu_id(i);
+		HFTEST_LOG("Booting CPU %u - %x", i, id);
+
+		EXPECT_EQ(
+			hftest_cpu_start(id, secondary_ec_stack[i - 1],
+					 sizeof(secondary_ec_stack[0]),
+					 migrate_busy_up_sp, (uintptr_t)&args),
+			true);
+
+		HFTEST_LOG("Done with CPU %u", i);
+	}
+
+	/*
+	 * Send command to receiver SP to send command to companion SP to sleep
+	 * there by putting receiver SP in BLOCKED state.
+	 */
+	res = sp_fwd_sleep_cmd_send(own_id, receiver_id, companion_id,
+				    SP_SLEEP_LONG, false);
+
+	EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+	EXPECT_EQ(sp_resp(res), SP_SUCCESS);
+}
diff --git a/test/vmapi/ffa_secure_partitions/ffa_secure_partitions.c b/test/vmapi/ffa_secure_partitions/ffa_secure_partitions.c
index 9236c7e..adec178 100644
--- a/test/vmapi/ffa_secure_partitions/ffa_secure_partitions.c
+++ b/test/vmapi/ffa_secure_partitions/ffa_secure_partitions.c
@@ -11,3 +11,29 @@
 SERVICE_PARTITION_INFO_GET(service1, SERVICE1)
 SERVICE_PARTITION_INFO_GET(service2, SERVICE2)
 SERVICE_PARTITION_INFO_GET(service3, SERVICE3)
+
+/**
+ * Helper to setup mailbox for precondition functions.
+ */
+static struct mailbox_buffers get_precondition_mailbox(void)
+{
+	static struct mailbox_buffers mb = {.recv = NULL, .send = NULL};
+
+	if (mb.send == NULL && mb.recv == NULL) {
+		mb = set_up_mailbox();
+	}
+
+	return mb;
+}
+
+/*
+ * The following is a precondition function, for the current system set-up.
+ * Check that service1 partition is an UP SP.
+ */
+bool service2_is_up_sp(void)
+{
+	struct mailbox_buffers mb = get_precondition_mailbox();
+	struct ffa_partition_info *service2_info = service2(mb.recv);
+
+	return (service2_info->vcpu_count == 1);
+}
diff --git a/test/vmapi/ffa_secure_partitions/inc/ffa_secure_partitions.h b/test/vmapi/ffa_secure_partitions/inc/ffa_secure_partitions.h
index 1611332..35c0110 100644
--- a/test/vmapi/ffa_secure_partitions/inc/ffa_secure_partitions.h
+++ b/test/vmapi/ffa_secure_partitions/inc/ffa_secure_partitions.h
@@ -52,3 +52,9 @@
 struct ffa_partition_info* service1(void* recv);
 struct ffa_partition_info* service2(void* recv);
 struct ffa_partition_info* service3(void* recv);
+
+/* Precondition functions for this test setup. */
+bool service2_is_up_sp(void);
+
+uint64_t syscounter_read(void);
+void waitms(uint64_t ms);
diff --git a/test/vmapi/ffa_secure_partitions/secure_interrupts.c b/test/vmapi/ffa_secure_partitions/secure_interrupts.c
index d869f5b..319a9c5 100644
--- a/test/vmapi/ffa_secure_partitions/secure_interrupts.c
+++ b/test/vmapi/ffa_secure_partitions/secure_interrupts.c
@@ -6,44 +6,19 @@
  * https://opensource.org/licenses/BSD-3-Clause.
  */
 
-#include "hf/arch/barriers.h"
 #include "hf/arch/irq.h"
+#include "hf/arch/vm/delay.h"
 #include "hf/arch/vm/interrupts_gicv3.h"
 #include "hf/arch/vm/timer.h"
 
 #include "ffa_secure_partitions.h"
 #include "gicv3.h"
-#include "msr.h"
 #include "partition_services.h"
 #include "sp_helpers.h"
 
 #define SP_SLEEP_TIME 400U
 #define NS_SLEEP_TIME 200U
 
-static inline uint64_t syscounter_read(void)
-{
-	isb();
-	return read_msr(cntvct_el0);
-}
-
-static void waitus(uint64_t us)
-{
-	uint64_t start_count_val = syscounter_read();
-	uint64_t wait_cycles = (us * read_msr(cntfrq_el0)) / 1000000;
-
-	while ((syscounter_read() - start_count_val) < wait_cycles) {
-		/* Busy wait... */;
-	}
-}
-
-static void waitms(uint64_t ms)
-{
-	while (ms > 0) {
-		waitus(1000);
-		ms--;
-	}
-}
-
 static void configure_trusted_wdog_interrupt(ffa_vm_id_t source,
 					     ffa_vm_id_t dest, bool enable)
 {