#include "kvm_util.h"
#include "vmx.h"
#include "svm_util.h"
#include "kselftest.h"
#define TEST_MEM_SLOT_INDEX 1
#define TEST_MEM_PAGES 2
#define TEST_MEM_BASE 0xc0000000
#define TEST_GUEST_ADDR(idx) (TEST_MEM_BASE + (idx) * PAGE_SIZE)
#define TEST_VMCB_L1_GPA(idx) TEST_GUEST_ADDR(idx)
#define TEST_VMCB_GVA(idx) TEST_GUEST_ADDR(idx)
#define TEST_VMCB_L2_GPA TEST_VMCB_L1_GPA(0)
#define L2_GUEST_STACK_SIZE 64
static void l2_guest_code_vmsave(void)
{
asm volatile("vmsave %0" : : "a"(TEST_VMCB_L2_GPA) : "memory");
}
static void l2_guest_code_vmload(void)
{
asm volatile("vmload %0" : : "a"(TEST_VMCB_L2_GPA) : "memory");
}
static void l2_guest_code_vmcb(int vmcb_idx)
{
wrmsr(MSR_KERNEL_GS_BASE, 0xaaaa);
l2_guest_code_vmsave();
GUEST_SYNC(vmcb_idx);
l2_guest_code_vmload();
GUEST_ASSERT_EQ(rdmsr(MSR_KERNEL_GS_BASE), 0xbbbb);
wrmsr(MSR_KERNEL_GS_BASE, 0);
l2_guest_code_vmsave();
vmmcall();
}
static void l2_guest_code_vmcb0(void)
{
l2_guest_code_vmcb(0);
}
static void l2_guest_code_vmcb1(void)
{
l2_guest_code_vmcb(1);
}
static void l1_guest_code(struct svm_test_data *svm)
{
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
generic_svm_setup(svm, NULL, &l2_guest_stack[L2_GUEST_STACK_SIZE]);
svm->vmcb->control.intercept |= (BIT_ULL(INTERCEPT_VMSAVE) |
BIT_ULL(INTERCEPT_VMLOAD));
svm->vmcb->control.virt_ext &= ~VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
svm->vmcb->save.rip = (u64)l2_guest_code_vmsave;
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMSAVE);
svm->vmcb->save.rip = (u64)l2_guest_code_vmload;
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMLOAD);
svm->vmcb->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
svm->vmcb->save.rip = (u64)l2_guest_code_vmsave;
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMSAVE);
svm->vmcb->save.rip = (u64)l2_guest_code_vmload;
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMLOAD);
svm->vmcb->control.intercept &= ~(BIT_ULL(INTERCEPT_VMSAVE) |
BIT_ULL(INTERCEPT_VMLOAD));
svm->vmcb->save.rip = (u64)l2_guest_code_vmcb0;
svm->vmcb->control.virt_ext &= ~VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMMCALL);
svm->vmcb->save.rip = (u64)l2_guest_code_vmcb1;
svm->vmcb->control.virt_ext |= VIRTUAL_VMLOAD_VMSAVE_ENABLE_MASK;
run_guest(svm->vmcb, svm->vmcb_gpa);
GUEST_ASSERT_EQ(svm->vmcb->control.exit_code, SVM_EXIT_VMMCALL);
GUEST_DONE();
}
int main(int argc, char *argv[])
{
vm_vaddr_t nested_gva = 0;
struct vmcb *test_vmcb[2];
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int i;
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_SVM));
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_NPT));
TEST_REQUIRE(kvm_cpu_has(X86_FEATURE_V_VMSAVE_VMLOAD));
vm = vm_create_with_one_vcpu(&vcpu, l1_guest_code);
vm_enable_tdp(vm);
vcpu_alloc_svm(vm, &nested_gva);
vcpu_args_set(vcpu, 1, nested_gva);
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS,
TEST_MEM_BASE, TEST_MEM_SLOT_INDEX,
TEST_MEM_PAGES, 0);
for (i = 0; i <= 1; i++) {
virt_map(vm, TEST_VMCB_GVA(i), TEST_VMCB_L1_GPA(i), 1);
test_vmcb[i] = (struct vmcb *)addr_gva2hva(vm, TEST_VMCB_GVA(i));
}
tdp_identity_map_default_memslots(vm);
TEST_ASSERT_EQ(TEST_VMCB_L2_GPA, TEST_VMCB_L1_GPA(0));
tdp_map(vm, TEST_VMCB_L2_GPA, TEST_VMCB_L1_GPA(1), PAGE_SIZE);
for (;;) {
struct ucall uc;
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
case UCALL_SYNC:
i = uc.args[1];
TEST_ASSERT(i == 0 || i == 1, "Unexpected VMCB idx: %d", i);
TEST_ASSERT_EQ(test_vmcb[i]->save.kernel_gs_base, 0xaaaa);
TEST_ASSERT_EQ(test_vmcb[1-i]->save.kernel_gs_base, 0);
test_vmcb[i]->save.kernel_gs_base = 0xbbbb;
break;
case UCALL_DONE:
goto done;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
}
done:
kvm_vm_free(vm);
return 0;
}