Skip to content

Commit 13a3eee

Browse files
rootBernhard Kaindl
authored andcommitted
Add initial minimal nested-svm-vmrun testcase
Based on an initial experiment by Ross Signed-off-by: Ross Lagerwall <ross.lagerwall@citrix.com> Signed-off-by: Bernhard Kaindl <bernhard.kaindl@citrix.com>
1 parent 453490d commit 13a3eee

6 files changed

Lines changed: 323 additions & 0 deletions

File tree

docs/all-tests.dox

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,6 @@ states.
206206
@subpage test-nested-svm - Nested SVM tests.
207207

208208
@subpage test-nested-vmx - Nested VT-x tests.
209+
210+
@subpage test-nested-svm-vmrun - Nested SVM VMRUN tests.
209211
*/

tests/nested-svm-vmrun/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
include $(ROOT)/build/common.mk
2+
3+
NAME := nested-svm-vmrun
4+
CATEGORY := in-development
5+
TEST-ENVS := hvm64
6+
7+
TEST-EXTRA-CFG := extra.cfg.in
8+
9+
obj-perenv += main.o entry.o
10+
11+
include $(ROOT)/build/gen.mk

tests/nested-svm-vmrun/entry.S

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* SVM nested-virt VMRUN trampoline.
3+
*
4+
* Calling convention (System V AMD64):
5+
* %rdi - physical address of the L2 (guest) VMCB
6+
*
7+
* VMRUN auto-saves a subset of host state to the area pointed to by
8+
* MSR_VM_HSAVE_PA and reloads it on #VMEXIT. Fields outside that subset
9+
* (FS/GS/LDTR/TR base, KernelGSBase, syscall MSRs, SYSENTER_*) are
10+
* exchanged via VMSAVE/VMLOAD on the L2 VMCB itself: VMLOAD before VMRUN
11+
* loads L2's copy; VMSAVE after #VMEXIT writes L2's back. This is
12+
* sufficient here because L2 inherits the same flat FS/GS as L1, so no
13+
* host-side preservation of those fields is required.
14+
*
15+
* GIF is left at 0 across the call: VMRUN expects GIF=1 at entry, so we
16+
* STGI before VMRUN, and after #VMEXIT GIF is 0 anyway. We do not STGI
17+
* on return; the framework runs with GIF=1 normally and #VMEXIT puts us
18+
* back in a non-nested context where leaving GIF clear is harmless for
19+
* the rest of the test.
20+
*/
21+
#include <xtf/asm_macros.h>
22+
23+
ENTRY(svm_vmrun)
24+
mov %rdi, %rax
25+
vmload %rax
26+
vmrun %rax
27+
vmsave %rax
28+
29+
ret
30+
ENDFUNC(svm_vmrun)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nestedhvm = 1

tests/nested-svm-vmrun/main.c

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* @file tests/nested-svm-vmrun/main.c
3+
* @ref test-nested-svm-vmrun
4+
*
5+
* @page test-nested-svm-vmrun nested-svm-vmrun
6+
*
7+
* Smoke-test of AMD SVM nested virtualisation: an L1 guest enables SVM,
8+
* builds a minimal L2 VMCB that re-uses L1's address space, and uses
9+
* VMRUN to enter an L2 callback. L2 writes a sentinel into shared
10+
* memory and signals completion with HLT, which causes a #VMEXIT back
11+
* to L1. The test passes if VMRUN succeeds, the exit reason is HLT,
12+
* and L1 observes the expected sentinel value.
13+
*
14+
* @see tests/nested-svm-vmrun/main.c
15+
*/
16+
#include <xtf.h>
17+
18+
#include "vmcb.h"
19+
20+
/* AMD MSRs. */
21+
#define MSR_VM_HSAVE_PA 0xc0010117U
22+
23+
const char test_title[] = "Nested SVM VMRUN";
24+
25+
/*
26+
* The L2 VMCB lives here. VMRUN auto-saves and restores the bulk of L1's
27+
* state via the host-save area pointed to by MSR_VM_HSAVE_PA.
28+
*/
29+
static struct vmcb l2_vmcb __page_aligned_bss;
30+
31+
/* Backing store for the VMRUN host-save area (MSR_VM_HSAVE_PA). */
32+
static uint8_t hsave[PAGE_SIZE] __page_aligned_bss;
33+
34+
/* Stack used by L2. Two pages of backing store. */
35+
static uint8_t l2_stack[2 * PAGE_SIZE] __page_aligned_bss;
36+
37+
/* Trampoline implemented in entry.S. */
38+
void svm_vmrun(unsigned long l2_vmcb_pa);
39+
40+
/* Sentinel written by L2 and verified by L1. */
41+
#define L2_SENTINEL 0xc0ffeeULL
42+
static volatile uint64_t l2_handshake;
43+
44+
/*
45+
* L2 entry point. L2 cannot use the inherited Xen hypercall page because
46+
* VMMCALL from L2 unconditionally takes a #VMEXIT to L1 in Xen's nested
47+
* SVM model, regardless of L1's VMCB intercepts. Instead, L2 writes a
48+
* sentinel into a shared variable (L1 and L2 share the same address
49+
* space) and signals completion with HLT, which L1 intercepts.
50+
*/
51+
static void __used l2_entry(void)
52+
{
53+
l2_handshake = L2_SENTINEL;
54+
for ( ;; )
55+
asm volatile ("hlt");
56+
}
57+
58+
/*
59+
* Populate the L2 VMCB with a flat long-mode environment that mirrors L1.
60+
* L2 shares L1's pagetables (cr3) and IDT/GDT, but runs on its own stack
61+
* and at its own RIP.
62+
*/
63+
static void build_l2_vmcb(void)
64+
{
65+
desc_ptr gdt_desc, idt_desc;
66+
67+
/* Intercept VMRUN (architecturally required) and HLT (used by L2 to
68+
* signal completion). Also intercept SHUTDOWN so L2 mistakes surface
69+
* as a recognisable VMEXIT rather than killing the L1 domain. */
70+
l2_vmcb.general1_intercepts =
71+
GENERAL1_INTERCEPT_HLT | GENERAL1_INTERCEPT_SHUTDOWN_EVT;
72+
l2_vmcb.general2_intercepts = GENERAL2_INTERCEPT_VMRUN;
73+
74+
l2_vmcb.asid = 1;
75+
76+
/* Inherit L1's paging/control state, EFER and RFLAGS. */
77+
l2_vmcb.cr0 = read_cr0();
78+
l2_vmcb.cr3 = read_cr3();
79+
l2_vmcb.cr4 = read_cr4();
80+
l2_vmcb.efer = rdmsr(MSR_EFER);
81+
l2_vmcb.rflags = read_flags();
82+
83+
l2_vmcb.rsp = _u(&l2_stack[sizeof(l2_stack)]);
84+
l2_vmcb.rip = _u(l2_entry);
85+
86+
/* Inherit L1's GDT/IDT. */
87+
sgdt(&gdt_desc);
88+
sidt(&idt_desc);
89+
l2_vmcb.gdtr.base = gdt_desc.base;
90+
l2_vmcb.gdtr.limit = gdt_desc.limit;
91+
l2_vmcb.idtr.base = idt_desc.base;
92+
l2_vmcb.idtr.limit = idt_desc.limit;
93+
94+
/*
95+
* Flat 64-bit segments, mirroring how XTF sets up L1.
96+
* Encoded segment attributes (P|DPL|S|Type|...|G|D/B|L|AVL):
97+
* CS: present, ring 0, code, exec/read, L=1 -> 0xa9b
98+
* DS/ES/SS/FS/GS: present, ring 3, data, read/write, G=1 -> 0xcf3 (DPL3)
99+
*/
100+
l2_vmcb.cs.sel = __KERN_CS;
101+
l2_vmcb.cs.attr = 0xa9b;
102+
l2_vmcb.cs.limit = ~0u;
103+
104+
l2_vmcb.ss.sel = __KERN_DS;
105+
l2_vmcb.ss.attr = 0xcf3;
106+
l2_vmcb.ss.limit = ~0u;
107+
108+
l2_vmcb.ds.sel = __USER_DS;
109+
l2_vmcb.ds.attr = 0xcf3;
110+
l2_vmcb.ds.limit = ~0u;
111+
112+
l2_vmcb.es = l2_vmcb.fs = l2_vmcb.gs = l2_vmcb.ds;
113+
}
114+
115+
void test_main(void)
116+
{
117+
if ( !cpu_has_svm )
118+
return xtf_skip("Skip: SVM not available\n");
119+
120+
/* Enable SVM and arm the host-save area. */
121+
wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME);
122+
wrmsr(MSR_VM_HSAVE_PA, _u(hsave));
123+
124+
build_l2_vmcb();
125+
126+
printk("L1: entering L2 via VMRUN\n");
127+
svm_vmrun(_u(&l2_vmcb));
128+
printk("L1: returned from L2 (exitcode 0x%lx, handshake 0x%lx)\n",
129+
l2_vmcb.exitcode, (unsigned long)l2_handshake);
130+
131+
if ( l2_vmcb.exitcode != VMEXIT_HLT )
132+
return xtf_failure("Fail: unexpected L2 exit 0x%lx (expected HLT 0x%x)\n",
133+
l2_vmcb.exitcode, VMEXIT_HLT);
134+
135+
if ( l2_handshake != L2_SENTINEL )
136+
return xtf_failure("Fail: L2 handshake 0x%lx != expected 0x%lx\n",
137+
(unsigned long)l2_handshake,
138+
(unsigned long)L2_SENTINEL);
139+
140+
xtf_success(NULL);
141+
}
142+
143+
/*
144+
* Local variables:
145+
* mode: C
146+
* c-file-style: "BSD"
147+
* c-basic-offset: 4
148+
* tab-width: 4
149+
* indent-tabs-mode: nil
150+
* End:
151+
*/

tests/nested-svm-vmrun/vmcb.h

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Minimal AMD SVM Virtual Machine Control Block layout.
3+
*
4+
* Only the fields required by the nested-svm-vmrun test are named; the rest
5+
* of the 4 KiB structure is preserved as anonymous reserved padding so the
6+
* named fields land at their architectural offsets. See AMD64 APM Volume 2,
7+
* Appendix B "Layout of VMCB" for the authoritative layout.
8+
*
9+
* This is intentionally not the full Xen vmcb_struct; in particular there are
10+
* no setter/getter accessors or VMCB clean-bit tracking. Tests can simply
11+
* write fields directly.
12+
*/
13+
#ifndef XTF_NESTED_SVM_VMRUN_VMCB_H
14+
#define XTF_NESTED_SVM_VMRUN_VMCB_H
15+
16+
#include <xtf/types.h>
17+
18+
struct vmcb_seg {
19+
uint16_t sel;
20+
uint16_t attr;
21+
uint32_t limit;
22+
uint64_t base;
23+
};
24+
25+
struct vmcb {
26+
/* Control area (offsets from start of VMCB). */
27+
uint32_t cr_intercepts; /* 0x000 */
28+
uint32_t dr_intercepts; /* 0x004 */
29+
uint32_t exception_intercepts; /* 0x008 */
30+
uint32_t general1_intercepts; /* 0x00C */
31+
uint32_t general2_intercepts; /* 0x010 */
32+
uint8_t _pad_014[0x040 - 0x014];
33+
uint64_t iopm_base_pa; /* 0x040 */
34+
uint64_t msrpm_base_pa; /* 0x048 */
35+
uint64_t tsc_offset; /* 0x050 */
36+
uint32_t asid; /* 0x058 */
37+
uint8_t tlb_control; /* 0x05C */
38+
uint8_t _pad_05d[3];
39+
uint64_t vintr; /* 0x060 */
40+
uint64_t int_state; /* 0x068 */
41+
uint64_t exitcode; /* 0x070 */
42+
uint64_t exitinfo1; /* 0x078 */
43+
uint64_t exitinfo2; /* 0x080 */
44+
uint64_t exit_int_info; /* 0x088 */
45+
uint64_t np_enable; /* 0x090 */
46+
uint8_t _pad_098[0x0a8 - 0x098];
47+
uint64_t event_inj; /* 0x0A8 */
48+
uint64_t h_cr3; /* 0x0B0 */
49+
uint8_t _pad_0b8[0x400 - 0x0b8];
50+
51+
/* State save area. Offsets are relative to 0x400. */
52+
struct vmcb_seg es; /* 0x400 */
53+
struct vmcb_seg cs; /* 0x410 */
54+
struct vmcb_seg ss; /* 0x420 */
55+
struct vmcb_seg ds; /* 0x430 */
56+
struct vmcb_seg fs; /* 0x440 */
57+
struct vmcb_seg gs; /* 0x450 */
58+
struct vmcb_seg gdtr; /* 0x460 */
59+
struct vmcb_seg ldtr; /* 0x470 */
60+
struct vmcb_seg idtr; /* 0x480 */
61+
struct vmcb_seg tr; /* 0x490 */
62+
uint8_t _pad_4a0[0x4cb - 0x4a0];
63+
uint8_t cpl; /* 0x4CB */
64+
uint32_t _pad_4cc;
65+
uint64_t efer; /* 0x4D0 */
66+
uint8_t _pad_4d8[0x548 - 0x4d8];
67+
uint64_t cr4; /* 0x548 */
68+
uint64_t cr3; /* 0x550 */
69+
uint64_t cr0; /* 0x558 */
70+
uint64_t dr7; /* 0x560 */
71+
uint64_t dr6; /* 0x568 */
72+
uint64_t rflags; /* 0x570 */
73+
uint64_t rip; /* 0x578 */
74+
uint8_t _pad_580[0x5d8 - 0x580];
75+
uint64_t rsp; /* 0x5D8 */
76+
uint8_t _pad_5e0[0x5f8 - 0x5e0];
77+
uint64_t rax; /* 0x5F8 */
78+
79+
uint8_t _pad_tail[0x1000 - 0x600];
80+
};
81+
82+
/* Compile-time verification of architectural offsets. */
83+
#define VMCB_CHECK(field, offset) \
84+
_Static_assert(__builtin_offsetof(struct vmcb, field) == (offset), \
85+
"VMCB layout mismatch: " #field)
86+
VMCB_CHECK(general1_intercepts, 0x00c);
87+
VMCB_CHECK(general2_intercepts, 0x010);
88+
VMCB_CHECK(asid, 0x058);
89+
VMCB_CHECK(exitcode, 0x070);
90+
VMCB_CHECK(es, 0x400);
91+
VMCB_CHECK(gdtr, 0x460);
92+
VMCB_CHECK(idtr, 0x480);
93+
VMCB_CHECK(tr, 0x490);
94+
VMCB_CHECK(efer, 0x4d0);
95+
VMCB_CHECK(cr4, 0x548);
96+
VMCB_CHECK(cr3, 0x550);
97+
VMCB_CHECK(cr0, 0x558);
98+
VMCB_CHECK(rflags, 0x570);
99+
VMCB_CHECK(rip, 0x578);
100+
VMCB_CHECK(rsp, 0x5d8);
101+
VMCB_CHECK(rax, 0x5f8);
102+
_Static_assert(sizeof(struct vmcb) == 0x1000, "VMCB size != 4 KiB");
103+
#undef VMCB_CHECK
104+
105+
/* General Intercept 1 register (offset 0x00C). */
106+
#define GENERAL1_INTERCEPT_HLT (1u << 24)
107+
#define GENERAL1_INTERCEPT_SHUTDOWN_EVT (1u << 31)
108+
109+
/* General Intercept 2 register (offset 0x010). */
110+
#define GENERAL2_INTERCEPT_VMRUN (1u << 0)
111+
#define GENERAL2_INTERCEPT_VMMCALL (1u << 1)
112+
113+
/* Selected VMEXIT exit codes. */
114+
#define VMEXIT_HLT 0x078
115+
#define VMEXIT_SHUTDOWN 0x07f
116+
#define VMEXIT_VMMCALL 0x081
117+
118+
#endif /* XTF_NESTED_SVM_VMRUN_VMCB_H */
119+
120+
/*
121+
* Local variables:
122+
* mode: C
123+
* c-file-style: "BSD"
124+
* c-basic-offset: 4
125+
* tab-width: 4
126+
* indent-tabs-mode: nil
127+
* End:
128+
*/

0 commit comments

Comments
 (0)