blob: e38a2fc9e375bd0ca8762f18f6bee61b3514a742 [file] [log] [blame]
/*
* Copyright 2019 The Hafnium Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "hf/arch/cpu.h"
#include "hf/arch/vm/interrupts_gicv3.h"
#include "hf/abi.h"
#include "hf/call.h"
#include "hf/spci.h"
#include "gicv3.h"
#include "hftest.h"
SET_UP(timer_secondary)
{
system_setup();
EXPECT_EQ(hf_vm_configure(send_page_addr, recv_page_addr), 0);
SERVICE_SELECT(SERVICE_VM0, "timer", send_buffer);
interrupt_enable(VIRTUAL_TIMER_IRQ, true);
interrupt_set_edge_triggered(VIRTUAL_TIMER_IRQ, true);
interrupt_set_priority_mask(0xff);
arch_irq_enable();
}
static void timer_busywait_secondary()
{
const char message[] = "loop 0099999";
const char expected_response[] = "Got IRQ 03.";
struct hf_vcpu_run_return run_res;
/* Let the secondary get started and wait for our message. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
/* Send the message for the secondary to set a timer. */
memcpy_s(send_buffer, SPCI_MSG_PAYLOAD_MAX, message, sizeof(message));
EXPECT_EQ(
spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM0, sizeof(message), 0)
.func,
SPCI_SUCCESS_32);
/*
* Let the secondary handle the message and set the timer. It will loop
* until the hardware interrupt fires, at which point we'll get and
* ignore the interrupt, and see a HF_VCPU_RUN_YIELD return code.
*/
dlog("running secondary after sending timer message.\n");
last_interrupt_id = 0;
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_PREEMPTED);
dlog("secondary yielded after receiving timer message\n");
EXPECT_EQ(last_interrupt_id, VIRTUAL_TIMER_IRQ);
/*
* Now that the timer has expired, when we call hf_vcpu_run again
* Hafnium should inject a virtual timer interrupt into the secondary,
* which should get it and respond.
*/
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
EXPECT_EQ(run_res.message.size, sizeof(expected_response));
EXPECT_EQ(memcmp(recv_buffer, expected_response,
sizeof(expected_response)),
0);
EXPECT_EQ(hf_mailbox_clear(), 0);
}
/**
* Send a message to the interruptible VM, which will start a timer to interrupt
* itself to send a response back.
*/
TEST(timer_secondary, busywait)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_busywait_secondary();
timer_busywait_secondary();
}
static void timer_secondary(const char message[],
enum hf_vcpu_run_code expected_code)
{
const char expected_response[] = "Got IRQ 03.";
size_t message_length = strnlen_s(message, 64) + 1;
struct hf_vcpu_run_return run_res;
/* Let the secondary get started and wait for our message. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
/* Send the message for the secondary to set a timer. */
memcpy_s(send_buffer, SPCI_MSG_PAYLOAD_MAX, message, message_length);
EXPECT_EQ(
spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM0, message_length, 0)
.func,
SPCI_SUCCESS_32);
/* Let the secondary handle the message and set the timer. */
last_interrupt_id = 0;
run_res = hf_vcpu_run(SERVICE_VM0, 0);
/*
* There's a race for whether the secondary manages to block and switch
* to the primary before the hardware timer fires, so we need to handle
* three cases:
* 1. The (hardware) timer fires immediately, we get
* HF_VCPU_RUN_PREEMPTED.
* 2. The secondary blocks and switches back, we get expected_code until
* the timer fires.
* 2a. The timer then expires while we are in the primary, so Hafnium
* can inject the timer interrupt the next time we call hf_vcpu_run.
* 2b. The timer fires while the secondary is running, so we get
* HF_VCPU_RUN_PREEMPTED as in case 1.
*/
if (run_res.code != expected_code &&
run_res.code != HF_VCPU_RUN_PREEMPTED) {
FAIL("Expected run to return HF_VCPU_RUN_PREEMPTED or %d, but "
"got %d",
expected_code, run_res.code);
}
/* Loop until the timer fires. */
while (run_res.code == expected_code) {
/*
* This case happens if the secondary manages to block and
* switch to the primary before the timer fires.
*/
dlog("Primary looping until timer fires\n");
if (expected_code == HF_VCPU_RUN_WAIT_FOR_INTERRUPT ||
expected_code == HF_VCPU_RUN_WAIT_FOR_MESSAGE) {
EXPECT_NE(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
dlog("%d ns remaining\n", run_res.sleep.ns);
}
run_res = hf_vcpu_run(SERVICE_VM0, 0);
}
dlog("Primary done looping\n");
if (run_res.code == HF_VCPU_RUN_PREEMPTED) {
/*
* This case happens if the (hardware) timer fires before the
* secondary blocks and switches to the primary, either
* immediately after setting the timer or during the loop above.
* Then we get the interrupt to the primary, ignore it, and see
* a HF_VCPU_RUN_PREEMPTED code from the hf_vcpu_run call, so we
* should call it again for the timer interrupt to be injected
* automatically by Hafnium.
*/
EXPECT_EQ(last_interrupt_id, VIRTUAL_TIMER_IRQ);
dlog("Preempted by timer interrupt, running again\n");
run_res = hf_vcpu_run(SERVICE_VM0, 0);
}
/* Once we wake it up it should get the timer interrupt and respond. */
EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
EXPECT_EQ(run_res.message.size, sizeof(expected_response));
EXPECT_EQ(memcmp(recv_buffer, expected_response,
sizeof(expected_response)),
0);
EXPECT_EQ(hf_mailbox_clear(), 0);
}
/**
* Send a message to the interruptible VM, which will start a timer to interrupt
* itself to send a response back. This test is run with both long and short
* timer lengths, to try to cover both cases of the race for whether the timer
* fires before or after the secondary VM blocks and switches back to the
* primary.
*/
TEST(timer_secondary, wfi_short)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_secondary("WFI 0000001", HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
timer_secondary("WFI 0000001", HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
}
TEST(timer_secondary, wfi_long)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_secondary("WFI 0099999", HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
timer_secondary("WFI 0099999", HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
}
TEST(timer_secondary, wfe_short)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_secondary("WFE 0000001", HF_VCPU_RUN_YIELD);
timer_secondary("WFE 0000001", HF_VCPU_RUN_YIELD);
}
TEST(timer_secondary, wfe_long)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_secondary("WFE 0099999", HF_VCPU_RUN_YIELD);
timer_secondary("WFE 0099999", HF_VCPU_RUN_YIELD);
}
TEST(timer_secondary, receive_short)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_secondary("RECV 0000001", HF_VCPU_RUN_WAIT_FOR_MESSAGE);
timer_secondary("RECV 0000001", HF_VCPU_RUN_WAIT_FOR_MESSAGE);
}
TEST(timer_secondary, receive_long)
{
/*
* Run the test twice in a row, to check that the state doesn't get
* messed up.
*/
timer_secondary("RECV 0099999", HF_VCPU_RUN_WAIT_FOR_MESSAGE);
timer_secondary("RECV 0099999", HF_VCPU_RUN_WAIT_FOR_MESSAGE);
}
/**
* Set the timer for a very long time, and expect that it doesn't fire.
*/
TEST(timer_secondary, wfi_very_long)
{
const char message[] = "WFI 9999999";
size_t message_length = strnlen_s(message, 64) + 1;
struct hf_vcpu_run_return run_res;
/* Let the secondary get started and wait for our message. */
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_MESSAGE);
EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
/* Send the message for the secondary to set a timer. */
memcpy_s(send_buffer, SPCI_MSG_PAYLOAD_MAX, message, message_length);
EXPECT_EQ(
spci_msg_send(HF_PRIMARY_VM_ID, SERVICE_VM0, message_length, 0)
.func,
SPCI_SUCCESS_32);
/*
* Let the secondary handle the message and set the timer.
*/
last_interrupt_id = 0;
for (int i = 0; i < 20; ++i) {
run_res = hf_vcpu_run(SERVICE_VM0, 0);
EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
dlog("Primary looping until timer fires; %d ns "
"remaining\n",
run_res.sleep.ns);
}
}