|
| 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 | + */ |
0 commit comments