#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include "test_util.h"
#include "kvm_util.h"
#include "smm.h"
#include "vmx.h"
#include "svm_util.h"
#define SMRAM_GPA 0x1000000
#define SMRAM_STAGE 0xfe
#define STR(x) #x
#define XSTR(s) STR(s)
#define SYNC_PORT 0xe
#define DONE 0xff
uint8_t smi_handler[] = {
0xb0, SMRAM_STAGE,
0xe4, SYNC_PORT,
0x0f, 0xaa,
};
static inline void sync_with_host(uint64_t phase)
{
asm volatile("in $" XSTR(SYNC_PORT)", %%al \n"
: "+a" (phase));
}
static void self_smi(void)
{
x2apic_write_reg(APIC_ICR,
APIC_DEST_SELF | APIC_INT_ASSERT | APIC_DM_SMI);
}
static void l2_guest_code(void)
{
sync_with_host(8);
sync_with_host(10);
vmcall();
}
static void guest_code(void *arg)
{
#define L2_GUEST_STACK_SIZE 64
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
uint64_t apicbase = rdmsr(MSR_IA32_APICBASE);
struct svm_test_data *svm = arg;
struct vmx_pages *vmx_pages = arg;
sync_with_host(1);
wrmsr(MSR_IA32_APICBASE, apicbase | X2APIC_ENABLE);
sync_with_host(2);
self_smi();
sync_with_host(4);
if (arg) {
if (this_cpu_has(X86_FEATURE_SVM)) {
generic_svm_setup(svm, l2_guest_code,
&l2_guest_stack[L2_GUEST_STACK_SIZE]);
} else {
GUEST_ASSERT(prepare_for_vmx_operation(vmx_pages));
GUEST_ASSERT(load_vmcs(vmx_pages));
prepare_vmcs(vmx_pages, l2_guest_code,
&l2_guest_stack[L2_GUEST_STACK_SIZE]);
}
sync_with_host(5);
self_smi();
sync_with_host(7);
if (this_cpu_has(X86_FEATURE_SVM)) {
run_guest(svm->vmcb, svm->vmcb_gpa);
run_guest(svm->vmcb, svm->vmcb_gpa);
} else {
vmlaunch();
vmresume();
}
sync_with_host(12);
}
sync_with_host(DONE);
}
int main(int argc, char *argv[])
{
vm_vaddr_t nested_gva = 0;
struct kvm_vcpu *vcpu;
struct kvm_regs regs;
struct kvm_vm *vm;
struct kvm_x86_state *state;
int stage, stage_reported;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_X86_SMM));
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
setup_smram(vm, vcpu, SMRAM_GPA, smi_handler, sizeof(smi_handler));
if (kvm_has_cap(KVM_CAP_NESTED_STATE)) {
if (kvm_cpu_has(X86_FEATURE_SVM))
vcpu_alloc_svm(vm, &nested_gva);
else if (kvm_cpu_has(X86_FEATURE_VMX))
vcpu_alloc_vmx(vm, &nested_gva);
}
if (!nested_gva)
pr_info("will skip SMM test with VMX enabled\n");
vcpu_args_set(vcpu, 1, nested_gva);
for (stage = 1;; stage++) {
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
memset(®s, 0, sizeof(regs));
vcpu_regs_get(vcpu, ®s);
stage_reported = regs.rax & 0xff;
if (stage_reported == DONE)
goto done;
TEST_ASSERT(stage_reported == stage ||
stage_reported == SMRAM_STAGE,
"Unexpected stage: #%x, got %x",
stage, stage_reported);
if (stage == 8) {
inject_smi(vcpu);
continue;
}
if (stage == 10)
inject_smi(vcpu);
state = vcpu_save_state(vcpu);
kvm_vm_release(vm);
vcpu = vm_recreate_with_one_vcpu(vm);
vcpu_load_state(vcpu, state);
kvm_x86_state_cleanup(state);
}
done:
kvm_vm_free(vm);
}