test: exercise reconfiguring an interrupt in runtime

This patch introduces a test case to validate that a Secure Partition
can request the SPMC to reconfigure an interrupt to be routed to a
secondary vCPU.

Signed-off-by: Madhukar Pappireddy <madhukar.pappireddy@arm.com>
Change-Id: I8b8ca86d1164d304216ab677378ef8399756f8f6
diff --git a/test/vmapi/ffa_secure_partitions/secure_interrupts.c b/test/vmapi/ffa_secure_partitions/secure_interrupts.c
index ad70d0f..7d24a55 100644
--- a/test/vmapi/ffa_secure_partitions/secure_interrupts.c
+++ b/test/vmapi/ffa_secure_partitions/secure_interrupts.c
@@ -9,6 +9,7 @@
 #include "hf/arch/irq.h"
 #include "hf/arch/vm/delay.h"
 #include "hf/arch/vm/interrupts_gicv3.h"
+#include "hf/arch/vm/power_mgmt.h"
 #include "hf/arch/vm/timer.h"
 
 #include "ffa_secure_partitions.h"
@@ -19,6 +20,19 @@
 #define SP_SLEEP_TIME 400U
 #define NS_SLEEP_TIME 200U
 
+#define LAST_SECONDARY_VCPU_ID (MAX_CPUS - 1)
+#define MID_SECONDARY_VCPU_ID (MAX_CPUS / 2)
+
+alignas(4096) static uint8_t secondary_ec_stack[MAX_CPUS - 1][PAGE_SIZE];
+
+struct secondary_cpu_entry_args {
+	ffa_id_t receiver_id;
+	ffa_vcpu_count_t vcpu_count;
+	ffa_vcpu_index_t vcpu_id;
+	struct spinlock lock;
+	ffa_vcpu_index_t target_vcpu_id;
+};
+
 static void configure_trusted_wdog_interrupt(ffa_id_t source, ffa_id_t dest,
 					     bool enable)
 {
@@ -340,3 +354,136 @@
 	EXPECT_EQ(sp_resp(res), SP_SUCCESS);
 	check_and_disable_trusted_wdog_timer(own_id, receiver_id);
 }
+
+static void cpu_entry_sp_sleep_loop(uintptr_t arg)
+{
+	ffa_id_t own_id = hf_vm_get_id();
+	struct ffa_value res;
+	struct secondary_cpu_entry_args *args =
+		// NOLINTNEXTLINE(performance-no-int-to-ptr)
+		(struct secondary_cpu_entry_args *)arg;
+	bool is_receiver_up_sp = args->vcpu_count == 1;
+
+	/*
+	 * Execution context(s) of secondary Secure Partitions need CPU cycles
+	 * to be allocated through FFA_RUN interface to reach message loop.
+	 */
+	if (is_receiver_up_sp) {
+		res = ffa_run(args->receiver_id, (ffa_vcpu_index_t)0);
+	} else {
+		res = ffa_run(args->receiver_id, args->vcpu_id);
+	}
+
+	EXPECT_EQ(ffa_func_id(res), FFA_MSG_WAIT_32);
+
+	/* Prepare for the trusted watchdog interrupt routed to target vCPU. */
+	if (args->vcpu_id == args->target_vcpu_id) {
+		res = sp_route_interrupt_to_target_vcpu_cmd_send(
+			own_id, args->receiver_id, args->target_vcpu_id,
+			IRQ_TWDOG_INTID);
+
+		EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+		EXPECT_EQ(sp_resp(res), SP_SUCCESS);
+
+		/*
+		 * Make sure that twdog timer triggers shortly before the
+		 * sleep duration ends.
+		 */
+		enable_trigger_trusted_wdog_timer(own_id, args->receiver_id,
+						  SP_SLEEP_TIME - 50);
+	}
+
+	/* Send request to the SP to sleep. */
+	res = sp_sleep_cmd_send(own_id, args->receiver_id, SP_SLEEP_TIME);
+	EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+	EXPECT_EQ(sp_resp(res), SP_SUCCESS);
+
+	/* Make sure elapsed time not less than sleep time. */
+	EXPECT_GE(sp_resp_value(res), SP_SLEEP_TIME);
+
+	/* Check for the last serviced secure virtual interrupt. */
+	res = sp_get_last_interrupt_cmd_send(own_id, args->receiver_id);
+
+	EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+	EXPECT_EQ(sp_resp(res), SP_SUCCESS);
+
+	/*
+	 * Expect the target execution context of Service2 SP to handle the
+	 * trusted watchdog interrupt succesfully.
+	 */
+	if (args->vcpu_id == args->target_vcpu_id) {
+		EXPECT_EQ(sp_resp_value(res), IRQ_TWDOG_INTID);
+	} else {
+		/*
+		 * Make sure Trusted Watchdog timer interrupt was not serviced
+		 * by this execution context.
+		 */
+		EXPECT_NE(sp_resp_value(res), IRQ_TWDOG_INTID);
+	}
+
+	/* Clear last serviced secure virtual interrupt. */
+	res = sp_clear_last_interrupt_cmd_send(own_id, args->receiver_id);
+
+	EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
+	EXPECT_EQ(sp_resp(res), SP_SUCCESS);
+
+	/* Releases the lock passed in. */
+	sl_unlock(&args->lock);
+	arch_cpu_stop();
+}
+
+static void sp_route_interrupt_to_secondary_vcpu_base(
+	struct secondary_cpu_entry_args args)
+{
+	/* Start secondary EC while holding lock. */
+	sl_lock(&args.lock);
+
+	for (ffa_vcpu_index_t i = 1; i < MAX_CPUS; i++) {
+		uintptr_t cpu_id;
+		ffa_vcpu_index_t hftest_cpu_index = MAX_CPUS - i;
+
+		cpu_id = hftest_get_cpu_id(hftest_cpu_index);
+		args.vcpu_id = i;
+		HFTEST_LOG("Booting CPU %u - %x", i, cpu_id);
+
+		EXPECT_EQ(hftest_cpu_start(cpu_id, secondary_ec_stack[i - 1],
+					   sizeof(secondary_ec_stack[0]),
+					   cpu_entry_sp_sleep_loop,
+					   (uintptr_t)&args),
+			  true);
+
+		/* Wait for CPU to release the lock. */
+		sl_lock(&args.lock);
+
+		HFTEST_LOG("Done with CPU %u", i);
+	}
+}
+
+/*
+ * Test a Secure Partition can request the SPMC to reconfigure an interrupt to
+ * be routed to a secondary vCPU.
+ */
+TEST(secure_interrupts, sp_route_interrupt_to_secondary_vcpu)
+{
+	struct secondary_cpu_entry_args args = {.lock = SPINLOCK_INIT};
+	struct mailbox_buffers mb = set_up_mailbox();
+	struct ffa_partition_info *service2_info = service2(mb.recv);
+	const ffa_id_t receiver_id = service2_info->vm_id;
+
+	args.receiver_id = receiver_id;
+	args.vcpu_count = service2_info->vcpu_count;
+
+	/*
+	 * Reconfigure the twdog interrupt to be routed to last secondary
+	 * execution context of SP.
+	 */
+	args.target_vcpu_id = LAST_SECONDARY_VCPU_ID;
+	sp_route_interrupt_to_secondary_vcpu_base(args);
+
+	/*
+	 * Reconfigure the twdog interrupt to be routed to mid secondary
+	 * execution context of SP.
+	 */
+	args.target_vcpu_id = MID_SECONDARY_VCPU_ID;
+	sp_route_interrupt_to_secondary_vcpu_base(args);
+}