root/tools/testing/selftests/kvm/x86/smm_test.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2018, Red Hat, Inc.
 *
 * Tests for SMM.
 */
#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

/*
 * This is compiled as normal 64-bit code, however, SMI handler is executed
 * in real-address mode. To stay simple we're limiting ourselves to a mode
 * independent subset of asm here.
 * SMI handler always report back fixed stage SMRAM_STAGE.
 */
uint8_t smi_handler[] = {
        0xb0, SMRAM_STAGE,    /* mov $SMRAM_STAGE, %al */
        0xe4, SYNC_PORT,      /* in $SYNC_PORT, %al */
        0x0f, 0xaa,           /* rsm */
};

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();
                }

                /* Stages 8-11 are eaten by SMM (SMRAM_STAGE reported instead) */
                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));

        /* Create VM */
        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(&regs, 0, sizeof(regs));
                vcpu_regs_get(vcpu, &regs);

                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);

                /*
                 * Enter SMM during L2 execution and check that we correctly
                 * return from it. Do not perform save/restore while in SMM yet.
                 */
                if (stage == 8) {
                        inject_smi(vcpu);
                        continue;
                }

                /*
                 * Perform save/restore while the guest is in SMM triggered
                 * during L2 execution.
                 */
                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);
}