blob: 7437ae7484df369a0ce3fd3fd7660757ef30a16b [file] [log] [blame]
/*
* Copyright 2024 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/interrupts.h"
#include "hf/arch/vm/interrupts_gicv3.h"
#include "hf/arch/vm/power_mgmt.h"
#include "vmapi/hf/call.h"
#include "gicv3.h"
#include "ipi_state.h"
#include "primary_with_secondary.h"
#include "test/hftest.h"
#include "test/semaphore.h"
/**
* Where the ipi_state struct is stored for the IPI tests.
* Used to track the IPI state across different threads in
* different endpoints.
*/
alignas(PAGE_SIZE) static uint8_t ipi_state_page[PAGE_SIZE];
/**
* Structure defined for usage in tests with multiple cores.
* Used to pass arguments from primary to secondary core.
*/
struct ipi_cpu_entry_args {
ffa_id_t service_id;
ffa_vcpu_count_t vcpu_count;
ffa_vcpu_index_t vcpu_id;
ffa_vcpu_index_t target_vcpu_id;
struct mailbox_buffers mb;
struct semaphore work_done;
};
/*
* Test secure interrupt handling while the Secure Partition runs in FFA_RUN
* partition runtime model with virtual interrupts potentially masked. This
* test helps to validate the functionality of the SPMC, which is to:
* - Intercept a FFA_MSG_WAIT invocation by the current SP in FFA_RUN partition
* runtime model, if there are pending virtual secure interrupts.
* - Resume the SP to handle the pending secure virtual interrupt.
*
* For orchestrating the above scenario, we leverage indirect messaging
* interface and allocate CPU cycles to the Secure Partition through FFA_RUN
* interface.
*/
TEST_PRECONDITION(secure_interrupts, preempted_by_secure_interrupt,
service1_is_not_vm)
{
struct ffa_value ret;
struct mailbox_buffers mb = set_up_mailbox();
const uint32_t delay = 100;
const uint32_t echo_payload;
ffa_id_t echo_sender;
ffa_id_t own_id = hf_vm_get_id();
struct ffa_partition_info *service1_info = service1(mb.recv);
SERVICE_SELECT(service1_info->vm_id, "sec_interrupt_preempt_msg",
mb.send);
/*
* Send an indirect message to convey the Secure Watchdog timer delay
* which serves as the source of the secure interrupt.
*/
ret = send_indirect_message(own_id, service1_info->vm_id, mb.send,
&delay, sizeof(delay), 0);
EXPECT_EQ(ret.func, FFA_SUCCESS_32);
/* Schedule message receiver through FFA_RUN interface. */
ret = ffa_run(service1_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_MSG_WAIT_32);
receive_indirect_message((void *)&echo_payload, sizeof(echo_payload),
mb.recv, &echo_sender);
HFTEST_LOG("Message echoed back: %#x", echo_payload);
EXPECT_EQ(echo_payload, delay);
EXPECT_EQ(echo_sender, service1_info->vm_id);
}
/**
* This test expects SP1 to have pended an interrupt for SP2, before SP2 has
* booted, following the boot protocol.
*
* TODO: Make this test applicable to S-EL0 and S-EL1 UP partitions.
*/
TEST_PRECONDITION(secure_interrupts, handle_interrupt_rtm_init,
service2_is_mp_sp)
{
struct ffa_value ret;
struct mailbox_buffers mb = set_up_mailbox();
struct ffa_partition_info *service2_info = service2(mb.recv);
SERVICE_SELECT(service2_info->vm_id, "check_interrupt_rtm_init_handled",
mb.send);
/* Schedule message receiver through FFA_RUN interface. */
ret = ffa_run(service2_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_YIELD_32);
}
/**
* Setups up SRI and returns the interrupt ID.
*/
uint32_t enable_sri(void)
{
struct ffa_value ret;
uint32_t sri_id;
dlog_verbose("Enabling the SRI");
gicv3_system_setup();
ret = ffa_features(FFA_FEATURE_SRI);
sri_id = ffa_feature_intid(ret);
interrupt_enable(sri_id, true);
interrupt_set_priority(sri_id, 0x10);
interrupt_set_edge_triggered(sri_id, false);
interrupt_set_priority_mask(0xff);
arch_irq_enable();
return sri_id;
}
/**
* Secondary CPU entrypoint.
* Requests the 'send_ipi' function in the designated FF-A endpoint.
* Sends the vCPU to be targeted by the IPI via indirect messaging.
*/
static void cpu_entry_send_ipi(uintptr_t arg)
{
struct ipi_cpu_entry_args *args =
// NOLINTNEXTLINE(performance-no-int-to-ptr)
(struct ipi_cpu_entry_args *)arg;
struct ffa_value ret;
const ffa_id_t own_id = hf_vm_get_id();
ASSERT_TRUE(args != NULL);
ASSERT_TRUE(args->vcpu_count > 1);
HFTEST_LOG("%s: Within secondary core... %u", __func__, args->vcpu_id);
SERVICE_SELECT_MP(args->service_id, "send_ipi", args->mb.send,
args->vcpu_id);
/* Run service. */
ret = ffa_run(args->service_id, args->vcpu_id);
EXPECT_EQ(ret.func, FFA_MSG_WAIT_32);
/* Send it the target vCPU ID. */
ret = send_indirect_message(own_id, args->service_id, args->mb.send,
&args->target_vcpu_id,
sizeof(args->target_vcpu_id), 0);
ASSERT_EQ(ret.func, FFA_SUCCESS_32);
EXPECT_EQ(ffa_run(args->service_id, args->vcpu_id).func, FFA_YIELD_32);
HFTEST_LOG("%s cpu done...", __func__);
/* Signal to primary core that test is complete.*/
semaphore_signal(&args->work_done);
arch_cpu_stop();
}
/**
* Test that Service1 can send IPI to vCPU0 from vCPU1, whilst vCPU0 is in
* running state.
* Test Sequence:
* - Bootstrap vCPU0 in the respective test service, such that it can initialise
* the IPI state.
* - Service1 vCPU0 terminates and leaves the IPI state not READY.
* - Start CPU1 and within it, invoke test service to send IPI. Test service
* waits for state machine to transition into READY state.
* - Resume Service1 vCPU0 such that it can set IPI state to READY.
*
* Failure in this test would be captured by timeout as Service1 vCPU0 would
* hang waiting for the IPI.
*/
TEST_PRECONDITION(ipi, receive_ipi_running_vcpu, service1_is_mp_sp)
{
struct mailbox_buffers mb = set_up_mailbox();
struct ffa_partition_info *service1_info = service1(mb.recv);
struct ffa_value ret;
struct ipi_cpu_entry_args vcpu1_args = {
.service_id = service1_info->vm_id,
.vcpu_count = service1_info->vcpu_count,
.vcpu_id = 1,
.target_vcpu_id = 0,
.mb = mb};
/* Initialize semaphores to sync primary and secondary cores. */
semaphore_init(&vcpu1_args.work_done);
SERVICE_SELECT(service1_info->vm_id, "receive_ipi_running", mb.send);
ret = ffa_run(service1_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_YIELD_32);
/* Bring-up the core that sends the IPI. */
ASSERT_TRUE(hftest_cpu_start(
hftest_get_cpu_id(vcpu1_args.vcpu_id),
hftest_get_secondary_ec_stack(vcpu1_args.vcpu_id),
cpu_entry_send_ipi, (uintptr_t)&vcpu1_args));
/*
* Resumes service1 in target vCPU0 so it sets IPI state to READY and
* handles IPI.
*/
ret = ffa_run(service1_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_YIELD_32);
/* Wait for secondary core to return before finishing the test. */
semaphore_wait(&vcpu1_args.work_done);
}
/**
* Test that Service1 can send IPI to vCPU0 from vCPU1, whilst vCPU0 is in
* waiting state and execution is in the normal world.
* Test Sequence:
* - Bootstrap vCPU0 and share memory with it to instanciate the IPI state. The
* vCPU0 terminates with FFA_MSG_WAIT, so it is in the waiting state.
* - Start CPU1 and within it, invoke test service to send IPI. Test service
* waits for state machine to transition into READY state.
* - NWd waits for the Schedule Reciever Interrupt, checks that Service1 vCPU0
* is reported by FFA_NOTIFICATION_INFO_GET as having an IPI pending
* and then runs Service1 vCPU0 to handle the IPI.
* - vCPU0 is resumed to handle the IPI virtual interrupt. It should attest
* state transitions into HANDLED from the interrupt handler.
*/
TEST_PRECONDITION(ipi, receive_ipi_waiting_vcpu_in_nwd, service1_is_mp_sp)
{
struct mailbox_buffers mb = set_up_mailbox();
struct ffa_partition_info *service1_info = service1(mb.recv);
struct ffa_value ret;
struct ipi_cpu_entry_args vcpu1_args = {
.service_id = service1_info->vm_id,
.vcpu_count = service1_info->vcpu_count,
.vcpu_id = 1,
.target_vcpu_id = 0,
.mb = mb};
ffa_id_t memory_receivers[] = {
service1_info->vm_id,
};
uint32_t sri_id;
uint32_t expected_lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
uint16_t expected_ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
/* Get ready to handle SRI. */
sri_id = enable_sri();
SERVICE_SELECT(service1_info->vm_id, "receive_ipi_waiting_vcpu",
mb.send);
ret = ffa_run(service1_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_MSG_WAIT_32);
/* Share memory to setup the IPI state structure. */
hftest_ipi_state_share_page_and_init((uint64_t)ipi_state_page,
memory_receivers, 1, mb.send);
/*
* Resumes service1 in target vCPU0 to retrieve memory and configure the
* IPI state.
*/
ret = ffa_run(service1_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_MSG_WAIT_32);
/* Initialize semaphores to sync primary and secondary cores. */
semaphore_init(&vcpu1_args.work_done);
/* Bring-up the core that sends the IPI. */
ASSERT_TRUE(hftest_cpu_start(
hftest_get_cpu_id(vcpu1_args.vcpu_id),
hftest_get_secondary_ec_stack(vcpu1_args.vcpu_id),
cpu_entry_send_ipi, (uintptr_t)&vcpu1_args));
/*
* Reset the last interrupt ID so we know the next SRI is relate to
* the IPI handling.
*/
last_interrupt_id = 0;
/*
* Set the state to READY such that vCPU1 injects IPI to target vCPU0.
*/
hftest_ipi_state_set(READY);
/* Wait for the SRI. */
while (last_interrupt_id != sri_id) {
interrupt_wait();
}
/* Check the target vCPU 0 is returned by FFA_NOTIFICATION_INFO_GET. */
expected_lists_sizes[0] = 1;
expected_ids[0] = service1_info->vm_id;
expected_ids[1] = 0;
ffa_notification_info_get_and_check(1, expected_lists_sizes,
expected_ids);
/* Resumes service1 in target vCPU 0 to handle IPI. */
ret = ffa_run(service1_info->vm_id, 0);
EXPECT_EQ(ret.func, FFA_YIELD_32);
/* Wait for secondary core to return before finishing the test. */
semaphore_wait(&vcpu1_args.work_done);
}
/**
* Test that Service1 can send IPI to vCPU0 from vCPU1, whilst vCPU0 is in
* waiting state and execution is in the secure world. Service2 is given access
* to a shared buffer, where Service1 would have instanciated the IPI state. At
* the appropriate timing, Service2 transitions IPI state into READY.
*
* Test Sequence:
* - Bootstrap vCPU0 and share memory with it to instanciate the IPI state. The
* vCPU0 terminates with FFA_MSG_WAIT, so it is in the waiting state.
* - Bootstrap Service2 vCPU0 in 'set_ipi_ready'. This gives it access to the
* IPI state.
* - Start CPU1 and within it, invoke test service to send IPI. Test service
* waits for state machine to transition into READY state.
* - Resume Service2 vCPU0 so execution is in the Secure World. At this point,
* Service2 transitions IPI state to READY, and waits for the IPI state to be
* Handled.
* - NWd vCPU0 is resumed by the Schedule Reciever Interrupt checks that
* Service1 vCPU0 is reported by FFA_NOTIFICATION_INFO_GET as having an IPI
* pending, and then runs Service1 vCPU0 to handle the IPI.
* - Service1 vCPU0 is resumed to handle the IPI virtual interrupt.
* It should attest state transitions into HANDLED from the interrupt handler.
* - Service2 vCPU0 is then run to check that it successfully runs and completes
* after being interrupted.
*/
TEST_PRECONDITION(ipi, receive_ipi_waiting_vcpu_in_swd, service1_is_mp_sp)
{
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);
struct ipi_cpu_entry_args vcpu1_args = {
.service_id = service1_info->vm_id,
.vcpu_count = service1_info->vcpu_count,
.vcpu_id = 1,
.target_vcpu_id = 0,
.mb = mb};
ffa_id_t memory_receivers[] = {
service1_info->vm_id,
service2_info->vm_id,
};
uint32_t sri_id;
uint32_t expected_lists_sizes[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
uint16_t expected_ids[FFA_NOTIFICATIONS_INFO_GET_MAX_IDS] = {0};
/* Get ready to handle SRI. */
sri_id = enable_sri();
/* Initialize semaphores to sync primary and secondary cores. */
semaphore_init(&vcpu1_args.work_done);
/* Service1 is to handle the IPI in vCPU0. */
SERVICE_SELECT(service1_info->vm_id, "receive_ipi_waiting_vcpu",
mb.send);
EXPECT_EQ(ffa_run(service1_info->vm_id, 0).func, FFA_MSG_WAIT_32);
SERVICE_SELECT(service2_info->vm_id, "set_ipi_ready", mb.send);
EXPECT_EQ(ffa_run(service2_info->vm_id, 0).func, FFA_MSG_WAIT_32);
hftest_ipi_state_share_page_and_init(
(uint64_t)ipi_state_page, memory_receivers,
ARRAY_SIZE(memory_receivers), mb.send);
/*
* Resumes service1/2 in target vCPU to retrieve the memory, and
* initialise the state.
*/
EXPECT_EQ(ffa_run(service1_info->vm_id, 0).func, FFA_MSG_WAIT_32);
EXPECT_EQ(ffa_run(service2_info->vm_id, 0).func, FFA_MSG_WAIT_32);
/* Bring-up the core that sends the IPI. */
ASSERT_TRUE(hftest_cpu_start(
hftest_get_cpu_id(vcpu1_args.vcpu_id),
hftest_get_secondary_ec_stack(vcpu1_args.vcpu_id),
cpu_entry_send_ipi, (uintptr_t)&vcpu1_args));
/*
* Reset the last interrupt ID so we know the next SRI is relate to
* the IPI handling.
*/
last_interrupt_id = 0;
/*
* Resume service2 to set IPI state to ready, and cause service1 in
* vCPU1 to send the IPI.
*/
EXPECT_EQ(ffa_run(service2_info->vm_id, 0).func, FFA_INTERRUPT_32);
/* Wait for the SRI. */
while (last_interrupt_id != sri_id) {
interrupt_wait();
}
/* Check the target vCPU 0 is returned by FFA_NOTIFICATION_INFO_GET. */
expected_lists_sizes[0] = 1;
expected_ids[0] = service1_info->vm_id;
expected_ids[1] = 0;
ffa_notification_info_get_and_check(1, expected_lists_sizes,
expected_ids);
/* Resumes service1 in target vCPU 0 to handle IPI. */
EXPECT_EQ(ffa_run(service1_info->vm_id, 0).func, FFA_YIELD_32);
/*
* Resume service2 to check it can run to completion after being
* interrupted.
*/
EXPECT_EQ(ffa_run(service2_info->vm_id, 0).func, FFA_YIELD_32);
/* Wait for secondary core to return before finishing the test. */
semaphore_wait(&vcpu1_args.work_done);
}