Commit | Line | Data |
---|---|---|
79904c9d VK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (C) 2018, Red Hat, Inc. | |
4 | * | |
5 | * Tests for SMM. | |
6 | */ | |
7 | #define _GNU_SOURCE /* for program_invocation_short_name */ | |
8 | #include <fcntl.h> | |
9 | #include <stdio.h> | |
10 | #include <stdlib.h> | |
11 | #include <stdint.h> | |
12 | #include <string.h> | |
13 | #include <sys/ioctl.h> | |
14 | ||
15 | #include "test_util.h" | |
16 | ||
17 | #include "kvm_util.h" | |
18 | ||
19 | #include "vmx.h" | |
8ec107c8 | 20 | #include "svm_util.h" |
79904c9d VK |
21 | |
22 | #define VCPU_ID 1 | |
23 | ||
24 | #define PAGE_SIZE 4096 | |
25 | ||
26 | #define SMRAM_SIZE 65536 | |
27 | #define SMRAM_MEMSLOT ((1 << 16) | 1) | |
28 | #define SMRAM_PAGES (SMRAM_SIZE / PAGE_SIZE) | |
29 | #define SMRAM_GPA 0x1000000 | |
30 | #define SMRAM_STAGE 0xfe | |
31 | ||
32 | #define STR(x) #x | |
33 | #define XSTR(s) STR(s) | |
34 | ||
35 | #define SYNC_PORT 0xe | |
36 | #define DONE 0xff | |
37 | ||
38 | /* | |
39 | * This is compiled as normal 64-bit code, however, SMI handler is executed | |
40 | * in real-address mode. To stay simple we're limiting ourselves to a mode | |
41 | * independent subset of asm here. | |
42 | * SMI handler always report back fixed stage SMRAM_STAGE. | |
43 | */ | |
44 | uint8_t smi_handler[] = { | |
45 | 0xb0, SMRAM_STAGE, /* mov $SMRAM_STAGE, %al */ | |
46 | 0xe4, SYNC_PORT, /* in $SYNC_PORT, %al */ | |
47 | 0x0f, 0xaa, /* rsm */ | |
48 | }; | |
49 | ||
50 | void sync_with_host(uint64_t phase) | |
51 | { | |
52 | asm volatile("in $" XSTR(SYNC_PORT)", %%al \n" | |
53 | : : "a" (phase)); | |
54 | } | |
55 | ||
56 | void self_smi(void) | |
57 | { | |
58 | wrmsr(APIC_BASE_MSR + (APIC_ICR >> 4), | |
59 | APIC_DEST_SELF | APIC_INT_ASSERT | APIC_DM_SMI); | |
60 | } | |
61 | ||
8ec107c8 | 62 | void guest_code(void *arg) |
79904c9d VK |
63 | { |
64 | uint64_t apicbase = rdmsr(MSR_IA32_APICBASE); | |
65 | ||
66 | sync_with_host(1); | |
67 | ||
68 | wrmsr(MSR_IA32_APICBASE, apicbase | X2APIC_ENABLE); | |
69 | ||
70 | sync_with_host(2); | |
71 | ||
72 | self_smi(); | |
73 | ||
74 | sync_with_host(4); | |
75 | ||
8ec107c8 VK |
76 | if (arg) { |
77 | if (cpu_has_svm()) | |
78 | generic_svm_setup(arg, NULL, NULL); | |
79 | else | |
80 | GUEST_ASSERT(prepare_for_vmx_operation(arg)); | |
79904c9d VK |
81 | |
82 | sync_with_host(5); | |
83 | ||
84 | self_smi(); | |
85 | ||
86 | sync_with_host(7); | |
87 | } | |
88 | ||
89 | sync_with_host(DONE); | |
90 | } | |
91 | ||
92 | int main(int argc, char *argv[]) | |
93 | { | |
8ec107c8 | 94 | vm_vaddr_t nested_gva = 0; |
79904c9d VK |
95 | |
96 | struct kvm_regs regs; | |
97 | struct kvm_vm *vm; | |
98 | struct kvm_run *run; | |
99 | struct kvm_x86_state *state; | |
100 | int stage, stage_reported; | |
101 | ||
102 | /* Create VM */ | |
103 | vm = vm_create_default(VCPU_ID, 0, guest_code); | |
104 | ||
105 | vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); | |
106 | ||
107 | run = vcpu_state(vm, VCPU_ID); | |
108 | ||
109 | vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, SMRAM_GPA, | |
110 | SMRAM_MEMSLOT, SMRAM_PAGES, 0); | |
111 | TEST_ASSERT(vm_phy_pages_alloc(vm, SMRAM_PAGES, SMRAM_GPA, SMRAM_MEMSLOT) | |
112 | == SMRAM_GPA, "could not allocate guest physical addresses?"); | |
113 | ||
114 | memset(addr_gpa2hva(vm, SMRAM_GPA), 0x0, SMRAM_SIZE); | |
115 | memcpy(addr_gpa2hva(vm, SMRAM_GPA) + 0x8000, smi_handler, | |
116 | sizeof(smi_handler)); | |
117 | ||
118 | vcpu_set_msr(vm, VCPU_ID, MSR_IA32_SMBASE, SMRAM_GPA); | |
119 | ||
120 | if (kvm_check_cap(KVM_CAP_NESTED_STATE)) { | |
8ec107c8 VK |
121 | if (kvm_get_supported_cpuid_entry(0x80000001)->ecx & CPUID_SVM) |
122 | vcpu_alloc_svm(vm, &nested_gva); | |
123 | else | |
124 | vcpu_alloc_vmx(vm, &nested_gva); | |
125 | vcpu_args_set(vm, VCPU_ID, 1, nested_gva); | |
79904c9d | 126 | } else { |
244c6b6d | 127 | pr_info("will skip SMM test with VMX enabled\n"); |
79904c9d VK |
128 | vcpu_args_set(vm, VCPU_ID, 1, 0); |
129 | } | |
130 | ||
131 | for (stage = 1;; stage++) { | |
132 | _vcpu_run(vm, VCPU_ID); | |
133 | TEST_ASSERT(run->exit_reason == KVM_EXIT_IO, | |
134 | "Stage %d: unexpected exit reason: %u (%s),\n", | |
135 | stage, run->exit_reason, | |
136 | exit_reason_str(run->exit_reason)); | |
137 | ||
138 | memset(®s, 0, sizeof(regs)); | |
139 | vcpu_regs_get(vm, VCPU_ID, ®s); | |
140 | ||
141 | stage_reported = regs.rax & 0xff; | |
142 | ||
143 | if (stage_reported == DONE) | |
144 | goto done; | |
145 | ||
146 | TEST_ASSERT(stage_reported == stage || | |
147 | stage_reported == SMRAM_STAGE, | |
148 | "Unexpected stage: #%x, got %x", | |
149 | stage, stage_reported); | |
150 | ||
151 | state = vcpu_save_state(vm, VCPU_ID); | |
152 | kvm_vm_release(vm); | |
153 | kvm_vm_restart(vm, O_RDWR); | |
837ec79b | 154 | vm_vcpu_add(vm, VCPU_ID); |
79904c9d VK |
155 | vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid()); |
156 | vcpu_load_state(vm, VCPU_ID, state); | |
157 | run = vcpu_state(vm, VCPU_ID); | |
158 | free(state); | |
159 | } | |
160 | ||
161 | done: | |
162 | kvm_vm_free(vm); | |
163 | } |