blob: 5bb984ea6623d3c68b157660fc1ef8981496cdc2 [file] [log] [blame]
/*
* 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/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"
#include "gicv3.h"
#include "partition_services.h"
#include "sp805.h"
#include "sp_helpers.h"
#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)
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)
{
struct ffa_value res;
res = sp_virtual_interrupt_cmd_send(source, dest, IRQ_TWDOG_INTID,
enable, 0);
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
}
static void enable_trusted_wdog_interrupt(ffa_id_t source, ffa_id_t dest)
{
configure_trusted_wdog_interrupt(source, dest, true);
}
static void disable_trusted_wdog_interrupt(ffa_id_t source, ffa_id_t dest)
{
configure_trusted_wdog_interrupt(source, dest, false);
}
static void enable_trigger_trusted_wdog_timer(ffa_id_t own_id,
ffa_id_t receiver_id,
uint32_t timer_ms)
{
struct ffa_value res;
/* Enable trusted watchdog interrupt as vIRQ in the secure side. */
enable_trusted_wdog_interrupt(own_id, receiver_id);
/*
* Send a message to the SP through direct messaging requesting it to
* start the trusted watchdog timer.
*/
res = sp_twdog_cmd_send(own_id, receiver_id, timer_ms);
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
}
static void check_and_disable_trusted_wdog_timer(ffa_id_t own_id,
ffa_id_t receiver_id)
{
struct ffa_value res;
/* Check for the last serviced secure virtual interrupt. */
res = sp_get_last_interrupt_cmd_send(own_id, receiver_id);
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
/* Make sure Trusted Watchdog timer interrupt was serviced. */
EXPECT_EQ(sp_resp_value(res), IRQ_TWDOG_INTID);
/* Disable Trusted Watchdog interrupt. */
disable_trusted_wdog_interrupt(own_id, receiver_id);
}
/*
* Test secure interrupt handling while the Secure Partition is in RUNNING
* state.
*/
TEST(secure_interrupts, sp_running)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
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;
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 400);
/* Send request to the SP to sleep. */
res = sp_sleep_cmd_send(own_id, receiver_id, SP_SLEEP_TIME, 0);
/*
* Secure interrupt should trigger during this time, SP will handle the
* trusted watchdog timer interrupt.
*/
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_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
/**
* Test secure interrupt handling while the Secure Partition runs with
* interrupts potentially masked. This test helps to validate the functionality
* of the SPMC to intercept a direct response message sent via
* FFA_MSG_SEND_DIRECT_RESP_32 if there are pending virtual secure interrupts
* and reschedule the partition to handle the pending interrupt.
*/
TEST(secure_interrupts, sp_direct_response_intercepted)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
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;
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 400);
/* Send request to the SP to sleep uninterrupted. */
res = sp_sleep_cmd_send(own_id, receiver_id, SP_SLEEP_TIME,
OPTIONS_MASK_INTERRUPTS);
/*
* Secure interrupt should trigger during this time, SP will handle the
* trusted watchdog timer interrupt.
*/
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_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
/**
* Test secure interrupt handling while the Secure Partition runs with
* interrupts potentially masked. This test helps to validate the functionality
* of the SPMC to intercept a direct response message sent via
* FFA_MSG_SEND_DIRECT_RESP2_64 if there are pending virtual secure interrupts
* and reschedule the partition to handle the pending interrupt.
*/
TEST(secure_interrupts, sp_direct_response2_intercepted)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
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;
const uint64_t msg[] = {SP_SLEEP_CMD, SP_SLEEP_TIME, 1};
struct ffa_uuid uuid;
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 400);
/* Send request to the SP to sleep uninterrupted. */
ffa_uuid_init(0, 0, 0, 0, &uuid);
res = ffa_msg_send_direct_req2(own_id, receiver_id, &uuid,
(const uint64_t *)&msg, ARRAY_SIZE(msg));
/*
* Secure interrupt should trigger during this time, SP will handle the
* trusted watchdog timer interrupt.
*/
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP2_64);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
/* Make sure elapsed time not less than sleep time. */
EXPECT_GE(res.arg5, SP_SLEEP_TIME);
check_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
/*
* This test is an extension of the 'sp_direct_response_intercepted' test. It
* creates a scenario where a direct response message between two Secure
* partitions in intercepted to signal a pending virtual secure interrupt.
*/
TEST(secure_interrupts, sp_forward_direct_response_intercepted)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
struct mailbox_buffers mb = set_up_mailbox();
struct ffa_partition_info *service1_info = service1(mb.recv);
struct ffa_partition_info *service2_info = service2(mb.recv);
const ffa_id_t receiver_id = service2_info->vm_id;
const ffa_id_t companion_id = service1_info->vm_id;
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 400);
/*
* Send request to the companion SP to send command to receiver SP to
* sleep uninterrupted.
*/
res = sp_fwd_sleep_cmd_send(own_id, companion_id, receiver_id,
SP_SLEEP_TIME, OPTIONS_MASK_INTERRUPTS);
/*
* Secure interrupt should trigger during this time, SP will handle the
* trusted watchdog timer interrupt.
*/
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
check_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
/*
* Test secure interrupt handling while the Secure Partition is in WAITING
* state.
*/
TEST(secure_interrupts, sp_waiting)
{
ffa_id_t own_id = hf_vm_get_id();
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;
uint64_t time1;
volatile uint64_t time_lapsed;
uint64_t timer_freq = read_msr(cntfrq_el0);
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 100);
time1 = syscounter_read();
/*
* Sleep for NS_SLEEP_TIME ms. This ensures secure wdog timer triggers
* during this time.
*/
waitms(NS_SLEEP_TIME);
/* Lapsed time should be at least equal to sleep time. */
time_lapsed = ((syscounter_read() - time1) * 1000) / timer_freq;
EXPECT_GE(time_lapsed, NS_SLEEP_TIME);
check_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
/*
* Test secure interrupt handling while the Secure Partition is in BLOCKED
* state.
*/
TEST(secure_interrupts, sp_blocked)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
struct mailbox_buffers mb = set_up_mailbox();
struct ffa_partition_info *service1_info = service1(mb.recv);
struct ffa_partition_info *service2_info = service2(mb.recv);
const ffa_id_t receiver_id = service2_info->vm_id;
const ffa_id_t companion_id = service1_info->vm_id;
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 400);
/*
* 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_TIME, 0);
/*
* Secure interrupt should trigger during this time, receiver SP will
* handle the trusted watchdog timer and sends direct response message.
*/
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
check_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
TEST(secure_interrupts, sp_preempted)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
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;
gicv3_system_setup();
interrupt_enable(PHYSICAL_TIMER_IRQ, true);
interrupt_set_priority(PHYSICAL_TIMER_IRQ, 0x80);
interrupt_set_edge_triggered(PHYSICAL_TIMER_IRQ, true);
interrupt_set_priority_mask(0xff);
arch_irq_enable();
/* Set physical timer for 20 ms and enable. */
write_msr(CNTP_TVAL_EL0, ns_to_ticks(20000000));
write_msr(CNTP_CTL_EL0, CNTx_CTL_ENABLE_MASK);
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 200);
/* Send request to receiver SP to sleep. */
res = sp_sleep_cmd_send(own_id, receiver_id, 50, 0);
/* SP is pre-empted by the non-secure timer interrupt. */
EXPECT_EQ(res.func, FFA_INTERRUPT_32);
/* VM id/vCPU index are passed through arg1. */
EXPECT_EQ(res.arg1, ffa_vm_vcpu(receiver_id, 0));
/* Waiting for interrupt to be serviced in normal world. */
while (last_interrupt_id == 0) {
EXPECT_EQ(io_read32_array(GICD_ISPENDR, 0), 0);
EXPECT_EQ(io_read32(GICR_ISPENDR0), 0);
EXPECT_EQ(io_read32_array(GICD_ISACTIVER, 0), 0);
EXPECT_EQ(io_read32(GICR_ISACTIVER0), 0);
}
/* Check that we got the interrupt. */
EXPECT_EQ(last_interrupt_id, PHYSICAL_TIMER_IRQ);
/* Check timer status. */
EXPECT_EQ(read_msr(CNTP_CTL_EL0),
CNTx_CTL_ISTS_MASK | CNTx_CTL_ENABLE_MASK);
/*
* NS Interrupt has been serviced and receiver SP is now in PREEMPTED
* state. Wait for trusted watchdog timer to be fired. SPMC queues
* the secure virtual interrupt.
*/
waitms(NS_SLEEP_TIME);
/*
* Resume the SP to complete the busy loop, handle the secure virtual
* interrupt and return with success.
*/
res = ffa_run(ffa_vm_id(res), ffa_vcpu_index(res));
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(res.arg3, SP_SUCCESS);
check_and_disable_trusted_wdog_timer(own_id, receiver_id);
}
/*
* Test Secure Partition runs to completion if it specifies action in response
* to Other-S Interrupt as queued.
*/
TEST(secure_interrupts, sp_other_s_interrupt_queued)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
struct mailbox_buffers mb = set_up_mailbox();
struct ffa_partition_info *service2_info = service2(mb.recv);
struct ffa_partition_info *service3_info = service3(mb.recv);
/*
* Service2 SP is the target of trusted watchdog timer interrupt.
* Service3 SP specified action to Other-S Interrupt as queued.
*/
const ffa_id_t target_id = service2_info->vm_id;
const ffa_id_t receiver_id = service3_info->vm_id;
enable_trigger_trusted_wdog_timer(own_id, target_id, 400);
/*
* Send command to receiver SP(Service3) to sleep for SP_SLEEP_TIME
* ms. Secure interrupt should trigger while SP is busy in running the
* sleep command. SPMC queues the virtual interrupt and resumes the
* SP.
*/
res = sp_sleep_cmd_send(own_id, receiver_id, SP_SLEEP_TIME, 0);
/* Service3 SP finishes and sends direct response back. */
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
/*
* Allocate cycles to target SP for it to handle the virtual secure
* interrupt.
*/
res = sp_sleep_cmd_send(own_id, target_id, 10, 0);
/*
* Secure interrupt should trigger during this time, SP will handle the
* trusted watchdog timer interrupt.
*/
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
/*
* Check if the trusted watchdog timer interrupt has been handled.
*/
check_and_disable_trusted_wdog_timer(own_id, target_id);
}
/*
* Test that an SP can attempt to yield CPU cycles while handling secure
* interrupt by invoking FFA_YIELD.
*/
TEST(secure_interrupts, sp_yield_sec_interrupt_handling)
{
struct ffa_value res;
ffa_id_t own_id = hf_vm_get_id();
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;
uint64_t time1;
volatile uint64_t time_lapsed;
uint64_t timer_freq = read_msr(cntfrq_el0);
/*
* Send command to SP asking it attempt to yield cycles while handling
* secure interrupt.
*/
res = sp_yield_secure_interrupt_handling_cmd_send(own_id, receiver_id,
true);
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
EXPECT_EQ(sp_resp(res), SP_SUCCESS);
enable_trigger_trusted_wdog_timer(own_id, receiver_id, 75);
time1 = syscounter_read();
/*
* Sleep for 100ms. This ensures secure wdog timer triggers
* during this time. SP starts handling secure interrupt but attempts
* to yields cycles. However, SPMC just resumes the SP to complete
* interrupt handling.
*/
waitms(100);
/* Lapsed time should be at least equal to sleep time. */
time_lapsed = ((syscounter_read() - time1) * 1000) / timer_freq;
EXPECT_GE(time_lapsed, 100);
res = sp_yield_secure_interrupt_handling_cmd_send(own_id, receiver_id,
false);
EXPECT_EQ(res.func, FFA_MSG_SEND_DIRECT_RESP_32);
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, 0);
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;
cpu_id = hftest_get_cpu_id(i);
args.vcpu_id = i;
HFTEST_LOG("Booting CPU %u - %lx", i, cpu_id);
EXPECT_EQ(hftest_cpu_start(
cpu_id, hftest_get_secondary_ec_stack(i),
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_LONG_RUNNING(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);
}