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);
+}