From 1c3bbd81e18ebfabe237f267668615959dc0a963 Mon Sep 17 00:00:00 2001 From: Bernhard Kaindl Date: Tue, 12 May 2026 09:00:00 +0000 Subject: [PATCH] Add initial minimal nested-svm-vmrun testcase Based on an initial experiment by Ross Signed-off-by: Ross Lagerwall Signed-off-by: Bernhard Kaindl --- docs/all-tests.dox | 2 + tests/nested-svm-vmrun/Makefile | 11 + tests/nested-svm-vmrun/entry.S | 37 ++++ tests/nested-svm-vmrun/extra.cfg.in | 1 + tests/nested-svm-vmrun/index.rst | 72 +++++++ tests/nested-svm-vmrun/main.c | 305 ++++++++++++++++++++++++++++ tests/nested-svm-vmrun/vmcb.h | 160 +++++++++++++++ 7 files changed, 588 insertions(+) create mode 100644 tests/nested-svm-vmrun/Makefile create mode 100644 tests/nested-svm-vmrun/entry.S create mode 100644 tests/nested-svm-vmrun/extra.cfg.in create mode 100644 tests/nested-svm-vmrun/index.rst create mode 100644 tests/nested-svm-vmrun/main.c create mode 100644 tests/nested-svm-vmrun/vmcb.h diff --git a/docs/all-tests.dox b/docs/all-tests.dox index ff387476..a005ffef 100644 --- a/docs/all-tests.dox +++ b/docs/all-tests.dox @@ -206,4 +206,6 @@ states. @subpage test-nested-svm - Nested SVM tests. @subpage test-nested-vmx - Nested VT-x tests. + +@subpage test-nested-svm-vmrun - Nested SVM VMRUN tests. */ diff --git a/tests/nested-svm-vmrun/Makefile b/tests/nested-svm-vmrun/Makefile new file mode 100644 index 00000000..3efe6e0c --- /dev/null +++ b/tests/nested-svm-vmrun/Makefile @@ -0,0 +1,11 @@ +include $(ROOT)/build/common.mk + +NAME := nested-svm-vmrun +CATEGORY := in-development +TEST-ENVS := hvm64 + +TEST-EXTRA-CFG := extra.cfg.in + +obj-perenv += main.o entry.o + +include $(ROOT)/build/gen.mk diff --git a/tests/nested-svm-vmrun/entry.S b/tests/nested-svm-vmrun/entry.S new file mode 100644 index 00000000..01d57129 --- /dev/null +++ b/tests/nested-svm-vmrun/entry.S @@ -0,0 +1,37 @@ +/* + * SVM nested-virt VMRUN trampoline. + * + * Calling convention (System V AMD64): + * %rdi - physical address of the L2 (guest) VMCB + * + * - VMRUN auto-saves a subset of host state to the area + * pointed to by MSR_VM_HSAVE_PA and reloads it on #VMEXIT. + * + * - Fields outside that subset + * (FS/GS/LDTR/TR base, KernelGSBase, syscall MSRs, SYSENTER_*) + * are exchanged via VMSAVE/VMLOAD on the L2 VMCB itself: + * + * - VMLOAD before VMRUN loads L2's copy; + * - VMSAVE after #VMEXIT writes L2's back. + * + * Shared memory: + * - This minimal test relies on L1 and L2 sharing compatible flat FS/GS state. + * so host-side preservation of FS/GS is not required for this minimal test. + * + * Enables interrupt handling: + * 1. Nested guest execution requires GIF enabled for normal interrupt + * delivery. En entry, we STGI (enable interrupts: GIF=1) before VMRUN. + # 2. #VMEXIT leaves GIF clear again, so we it by STGI before returning to L1. + */ +#include + +ENTRY(svm_vmrun) + mov %rdi, %rax + vmload %rax + stgi + vmrun %rax + vmsave %rax + stgi + + ret +ENDFUNC(svm_vmrun) diff --git a/tests/nested-svm-vmrun/extra.cfg.in b/tests/nested-svm-vmrun/extra.cfg.in new file mode 100644 index 00000000..ae494f84 --- /dev/null +++ b/tests/nested-svm-vmrun/extra.cfg.in @@ -0,0 +1 @@ +nestedhvm = 1 diff --git a/tests/nested-svm-vmrun/index.rst b/tests/nested-svm-vmrun/index.rst new file mode 100644 index 00000000..7fbdaaa2 --- /dev/null +++ b/tests/nested-svm-vmrun/index.rst @@ -0,0 +1,72 @@ +Nested SVM VMRUN +================ + +This test exercises a minimal AMD nested-SVM guest entry path on hvm64. + +An L1 guest enables SVM, builds an L2 VMCB that reuses L1's page tables and +descriptor tables, mirrors the active TR and LDTR state into the VMCB, and +enters L2 with VMRUN. The L2 payload runs on its own stack, writes a sentinel +into shared memory, and halts. + +The test passes when L1 observes a HLT VMEXIT from L2 and the expected +sentinel value. + +Why This Test Matters +--------------------- + +Nested-SVM support is only useful if an L1 guest can construct a valid VMCB +and successfully launch an L2 guest with VMRUN. This test covers that minimum +working path without depending on a large L2 payload or complex guest state. + +That makes it a focused regression test for the architectural plumbing needed +for nested guest entry: SVM enablement in EFER, host-save area setup through +MSR_VM_HSAVE_PA, VMCB population, and the return path back to L1 via a nested +VMEXIT. + +What It Verifies +---------------- + +The test verifies that Xen's nested-SVM implementation accepts a minimal but +architecturally valid L2 VMCB built by an L1 guest running in long mode. + +More specifically, it verifies that: + +* L1 can enable SVM and program the host-save area needed by VMRUN. +* L1 can populate an L2 VMCB with inherited control state, descriptor-table + state, and system-segment state taken from the live L1 environment. +* VMRUN succeeds in entering L2 rather than failing immediately because of + malformed guest state. +* L2 executes the supplied payload on its own stack, updates shared memory, + and exits through HLT. +* L1 receives a VMEXIT with exit code VMEXIT_HLT and observes the sentinel + value written by L2. + +How The Verification Functions Work +----------------------------------- + +The verification logic is split between the helpers that build a +valid VMCB and the final checks performed after returning from VMRUN. + +``build_l2_vmcb()`` prepares the nested guest state. It programs the +required intercepts, reuses L1's paging and descriptor-table state, +assigns the L2 RIP and stack, and copies LDTR and TR from the current +GDT into the VMCB. + +The helper ``vmcb_set_seg_desc()`` translates an L1 selector into the +VMCB segment format, while rejecting selectors that are null, LDT-based, +or out of bounds for the current GDT limit. This matters because VMRUN +consumes the VMCB's segment state directly, including the system +descriptors needed for long-mode execution. + +``l2_entry()`` is the L2 payload. It avoids using VMMCALL, because +in Xen's nested-SVM model that would unconditionally cause a VMEXIT +to L1. Instead, it writes a known sentinel value into shared memory +and halts in a loop so that L1 sees a clean HLT exit reason. + +``test_main()`` performs the end-to-end verification. It enables SVM, +writes the host-save area address to MSR_VM_HSAVE_PA, builds the VMCB, +and enters L2 through ``svm_vmrun()``. + +When execution returns to L1, the test checks two conditions: the VMEXIT +reason must be ``VMEXIT_HLT``, and the shared handshake value must match +``L2_SENTINEL``. Both checks must pass for the test to report success. diff --git a/tests/nested-svm-vmrun/main.c b/tests/nested-svm-vmrun/main.c new file mode 100644 index 00000000..b9c27640 --- /dev/null +++ b/tests/nested-svm-vmrun/main.c @@ -0,0 +1,305 @@ +/** + * @file tests/nested-svm-vmrun/main.c + * @ref test-nested-svm-vmrun + * + * @page test-nested-svm-vmrun nested-svm-vmrun + * + * Smoke-test of AMD SVM nested virtualisation: + * + * An L1 guest: + * 1. enables SVM, + * 2. builds a minimal L2 VMCB that re-uses L1's address space, and + * 3. uses VMRUN to enter an L2 callback. + + * L2: + * 1. writes a sentinel into shared memory, and + * 2. signals completion with HLT, which causes a #VMEXIT back to L1. + + * The test passes if: + * 1. VMRUN succeeds, + * 2. the exit reason is HLT, and + * 3. L1 observes the expected sentinel value. + * + * @include tests/nested-svm-vmrun/index.rst + * + * @see tests/nested-svm-vmrun/main.c + */ +#include + +#include "vmcb.h" + +/* AMD MSRs. */ +#define MSR_VM_HSAVE_PA 0xc0010117U + +const char test_title[] = "Nested SVM VMRUN"; + +/* + * The L2 VMCB lives here. VMRUN auto-saves and restores the bulk of + * L1's state via the host-save area pointed to by MSR_VM_HSAVE_PA. + */ +static struct vmcb l2_vmcb __page_aligned_bss; + +/* Backing store for the VMRUN host-save area (MSR_VM_HSAVE_PA). */ +static uint8_t hsave[PAGE_SIZE] __page_aligned_bss; + +/* Stack used by L2. Two pages of backing store. */ +static uint8_t l2_stack[2 * PAGE_SIZE] __page_aligned_bss; + +/** + * Enter L2 using the VMRUN trampoline in entry.S. + * @param l2_vmcb_pa Physical address of the L2 VMCB to run. + * + * The trampoline issues VMLOAD, VMRUN and VMSAVE around the nested guest entry + * so that L2 consumes the segment and state fields prepared in @c l2_vmcb. + */ +void svm_vmrun(unsigned long l2_vmcb_pa); + +/* Sentinel written by L2 and verified by L1. */ +#define L2_SENTINEL 0xc0ffeeULL +static volatile uint64_t l2_handshake; + +/** + * Convert an XTF segment descriptor into the VMCB attribute encoding. + * @param desc Descriptor to translate. + * @return VMCB segment attribute bits for @p desc. + * + * The VMCB stores descriptor limit[19:16] in attr[11:8], so the conversion + * is not a direct copy of the x86 descriptor access bits. + */ +static uint16_t user_desc_vmcb_attr(const user_desc *desc) +{ + return desc->type | + (desc->s << 4) | + (desc->dpl << 5) | + (desc->p << 7) | + /* Descriptor limit[19:16] is encoded in attr[11:8]. */ + (desc->limit1 << 8) | + (desc->avl << 12) | + (desc->l << 13) | + (desc->d << 14) | + (desc->g << 15); +} + +/** + * Tell whether a selector names the architecturally null descriptor. + * @param sel Selector to inspect. + * @return True if @p sel references GDT entry 0 regardless of RPL. + */ +static bool selector_is_null(uint16_t sel) +{ + return !(sel & ~(X86_SEL_TI | X86_SEL_RPL_MASK)); +} + +/** + * Mark a VMCB segment as unusable. + * @param seg Segment state to update. + * @param sel Selector value to retain in the VMCB. + * + * Null and architecturally invalid selectors are represented in the VMCB by + * a zero attribute field with zero base and limit. + */ +static void vmcb_set_seg_unusable(struct vmcb_seg *seg, uint16_t sel) +{ + seg->sel = sel; + seg->attr = 0; + seg->limit = 0; + seg->base = 0; +} + +/** + * Populate a VMCB segment field from an L1 GDT selector. + * @param seg Segment field in the VMCB to update. + * @param gdt Base of the L1 GDT. + * @param gdt_limit Inclusive byte limit of the GDT. + * @param sel Selector to decode. + * + * The helper rejects LDT selectors because this test only reuses the current + * GDT. In long mode, system descriptors such as TR and LDTR consume two GDT + * slots, so both slots must fit within @p gdt_limit before the descriptor is + * copied into the VMCB. + */ +static void vmcb_set_seg_desc(struct vmcb_seg *seg, const user_desc *gdt, + uint16_t gdt_limit, uint16_t sel) +{ + uint16_t sel_offset = sel & ~(X86_SEL_TI | X86_SEL_RPL_MASK); + unsigned int gdt_desc_bytes = sizeof(*gdt); + const user_desc *desc; + + if ( selector_is_null(sel) ) + { + vmcb_set_seg_unusable(seg, sel); + return; + } + + /* Verify the descriptor's first slot still lies within the GDT limit. */ + if ( (sel & X86_SEL_TI) || + (sel_offset + gdt_desc_bytes - 1 > gdt_limit) ) + { + vmcb_set_seg_unusable(seg, 0); + return; + } + + desc = (const user_desc *)((const char *)gdt + sel_offset); + + /* 64-bit long mode system descriptors (TR/LDTR) require two GDT slots. */ + if ( !desc->s ) + gdt_desc_bytes *= 2; + + /* Verify the descriptor's last byte still lies within the GDT limit. */ + if ( sel_offset + gdt_desc_bytes - 1 > gdt_limit ) + { + vmcb_set_seg_unusable(seg, 0); + return; + } + + seg->sel = sel; + seg->attr = user_desc_vmcb_attr(desc); + seg->limit = user_desc_limit(desc); + seg->base = user_desc_base(desc); +} + +/** + * Run a minimal L2 payload and report success back to L1. + * + * L2 cannot use the inherited Xen hypercall page here because VMMCALL from L2 + * unconditionally causes a #VMEXIT to L1 in Xen's nested-SVM model. Instead, + * L2 writes a sentinel into shared memory and halts so L1 observes a clean + * HLT exit reason. + */ +static void __used l2_entry(void) +{ + l2_handshake = L2_SENTINEL; + for ( ;; ) + asm volatile ("hlt"); +} + +/** + * Build the VMCB state used for the nested L2 guest. + * + * The test reuses L1's paging structures and descriptor tables, but supplies + * its own RIP and stack so L2 can execute a small payload in L1's address + * space. The resulting VMCB is intentionally minimal: it only carries the + * control and segment state needed for the VMRUN smoke test. + */ +static void build_l2_vmcb(void) +{ + desc_ptr gdt_desc, idt_desc; + const user_desc *gdt; + + /* Intercept VMRUN (architecturally required) and HLT (used by L2 to + * signal completion). Also intercept SHUTDOWN so L2 mistakes surface + * as a recognisable VMEXIT rather than killing the L1 domain. */ + l2_vmcb.intercept_insns_vec3 = + GENERAL1_INTERCEPT_HLT | GENERAL1_INTERCEPT_SHUTDOWN_EVT; + l2_vmcb.intercept_insns_vec4 = GENERAL2_INTERCEPT_VMRUN; + + /* ASID 0 is reserved for the host/hypervisor, so use ASID 1 for L2. */ + l2_vmcb.asid = 1; + + /* + * Inherit L1's paging/control state, EFER and RFLAGS. + * + * This is a standard minimal nested test technique that avoids + * the need to set up full paging and control structures for L2, + * and sharing the GDT/IDT is acceptable for this smoke test. + */ + l2_vmcb.cr0 = read_cr0(); + l2_vmcb.cr3 = read_cr3(); + l2_vmcb.cr4 = read_cr4(); + l2_vmcb.efer = rdmsr(MSR_EFER); + l2_vmcb.rflags = read_flags(); + + l2_vmcb.rsp = _u(&l2_stack[sizeof(l2_stack)]); + l2_vmcb.rip = _u(l2_entry); + + /* Inherit L1's GDT/IDT. */ + sgdt(&gdt_desc); + sidt(&idt_desc); + l2_vmcb.gdtr.base = gdt_desc.base; + l2_vmcb.gdtr.limit = gdt_desc.limit; + l2_vmcb.idtr.base = idt_desc.base; + l2_vmcb.idtr.limit = idt_desc.limit; + gdt = (const user_desc *)gdt_desc.base; + + /* + * Mirror the active LDTR/TR from L1 into the L2 VMCB. This test shares + * L1's GDT, and VMLOAD/VMRUN consume LDTR/TR from the VMCB rather than + * reconstructing them from the live CPU state. Copying the current L1 + * descriptors keeps L2's system-segment state self-consistent with the + * shared GDT; in particular, TR continues to describe the active TSS used + * for long-mode event delivery (RSP0/IST), while any non-null LDTR state + * is carried across unchanged. + */ + vmcb_set_seg_desc(&l2_vmcb.ldtr, gdt, gdt_desc.limit, sldt()); + vmcb_set_seg_desc(&l2_vmcb.tr, gdt, gdt_desc.limit, str()); + + /* + * Flat 64-bit segments, mirroring how XTF sets up L1. + * Encoded segment attributes (P|DPL|S|Type|...|G|D/B|L|AVL): + */ + + /* CS: present, ring 0, code, exec/read L=1 -> 0xa9b */ + l2_vmcb.cs.sel = __KERN_CS; + l2_vmcb.cs.attr = 0xa9b; + l2_vmcb.cs.limit = ~0u; + + /* DS/ES/FS/GS: present, ring 3, data, read/write, G=1 -> 0xcf3 (DPL3) */ + l2_vmcb.ds.sel = __USER_DS; + l2_vmcb.ds.attr = 0xcf3; + l2_vmcb.ds.limit = ~0u; + l2_vmcb.es = l2_vmcb.fs = l2_vmcb.gs = l2_vmcb.ds; + + /* + * SS: Long mode might ignore segment bases and limits, but it might still + * require valid values for architectural reasons. + * Same as above: present, ring 3, data, read/write, G=1 -> 0xcf3 (DPL3) + */ + l2_vmcb.ss.sel = __KERN_DS; + l2_vmcb.ss.attr = 0xcf3; + l2_vmcb.ss.limit = ~0u; +} + +/** + * Execute the nested-SVM VMRUN smoke test. + * + * L1 enables SVM, prepares a minimal L2 VMCB, enters L2 once with VMRUN and + * verifies that L2 reports success by writing the expected sentinel before + * exiting with HLT. + */ +void test_main(void) +{ + if ( !cpu_has_svm ) + return xtf_skip("Skip: SVM not available\n"); + + /* Enable SVM and arm the host-save area. */ + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME); + wrmsr(MSR_VM_HSAVE_PA, _u(hsave)); + + build_l2_vmcb(); + + printk("L1: entering L2 via VMRUN\n"); + svm_vmrun(_u(&l2_vmcb)); + printk("L1: returned from L2 (exitcode 0x%lx, handshake 0x%lx)\n", + l2_vmcb.exitcode, (unsigned long)l2_handshake); + + if ( l2_vmcb.exitcode != VMEXIT_HLT ) + return xtf_failure("Fail: unexpected L2 exit 0x%lx (expected HLT 0x%x)\n", + l2_vmcb.exitcode, VMEXIT_HLT); + + if ( l2_handshake != L2_SENTINEL ) + return xtf_failure("Fail: L2 handshake 0x%lx != expected 0x%lx\n", + (unsigned long)l2_handshake, + (unsigned long)L2_SENTINEL); + + xtf_success(NULL); +} + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/tests/nested-svm-vmrun/vmcb.h b/tests/nested-svm-vmrun/vmcb.h new file mode 100644 index 00000000..0b2f6b84 --- /dev/null +++ b/tests/nested-svm-vmrun/vmcb.h @@ -0,0 +1,160 @@ +/* + * Header file defining the minimal VMCB structure and related constants used + * by the nested-SVM VMRUN smoke test. + */ +#ifndef XTF_TESTS_NESTED_SVM_VMRUN_VMCB_H +#define XTF_TESTS_NESTED_SVM_VMRUN_VMCB_H + +#include + +/* Structure representing a segment register in the VMCB state save area. */ +struct vmcb_seg { + /* Segment selector. */ + uint16_t sel; + /* Segment attributes, encoding type, DPL, P, AVL, L, D/B and G bits. */ + uint16_t attr; + /* Segment limit. */ + uint32_t limit; + /* Segment base address. */ + uint64_t base; +}; + +/* + * Minimal AMD SVM Virtual Machine Control Block layout. + * + * Only the fields required by the nested-svm-vmrun test are named; the rest + * of the 4 KiB structure is preserved as anonymous reserved padding so the + * named fields land at their architectural offsets. + * + * See AMD64 APM Volume 2, Appendix B VMCB Layout for the authoritative layout. + * + * This is intentionally not the full Xen vmcb_struct: + * + * The structure is only intended to support the initial nested-SVM VMRUN + * smoke test, and may be refactored as the nested-SVM tests evolve. + * + * The offsets of the fields that are used are verified with static assertions + * to ensure the structure layout matches the architectural VMCB layout. + */ +struct vmcb { + /* Control area (offsets from start of VMCB). */ + + /* vec0: Read (0-15) and write (16-31) intercept bits for CR registers. */ + uint16_t intercept_read_cr; /* 0x000 */ + uint16_t intercept_write_cr; /* 0x002 */ + + /* vec1: Read (0-15) and write (16-31) intercept bits for DR registers. */ + uint16_t intercept_read_dr; /* 0x004 */ + uint16_t intercept_write_dr; /* 0x006 */ + + /* vec2: Intercept bits (0-31) for exceptions. */ + uint32_t intercept_exceptions; /* 0x008 */ + + /* vec3,4,5: Intercept bits for instructions (0-31). */ + uint32_t intercept_insns_vec3; /* 0x00C */ + uint32_t intercept_insns_vec4; /* 0x010 */ + uint32_t intercept_insns_vec5; /* 0x014 */ + + uint8_t _pad_018[0x03C - 0x018]; /* Reserved area up to offset 0x03C. */ + + uint16_t pause_filter_threshold; /* 0x03C */ + uint16_t pause_filter_count; /* 0x03E */ + uint64_t iopm_base_pa; /* 0x040 */ + uint64_t msrpm_base_pa; /* 0x048 */ + uint64_t tsc_offset; /* 0x050 */ + uint32_t asid; /* 0x058 */ + uint8_t tlb_control; /* 0x05C */ + uint8_t _pad_05d[3]; + uint64_t vintr; /* 0x060 */ + uint64_t int_state; /* 0x068 */ + uint64_t exitcode; /* 0x070 */ + uint64_t exitinfo1; /* 0x078 */ + uint64_t exitinfo2; /* 0x080 */ + uint64_t exit_int_info; /* 0x088 */ + uint64_t np_enable; /* 0x090 */ + uint8_t _pad_098[0x0a8 - 0x098]; + uint64_t event_inj; /* 0x0A8 */ + uint64_t h_cr3; /* 0x0B0 */ + uint8_t _pad_0b8[0x400 - 0x0b8]; + + /* State save area. Offsets are relative to 0x400. */ + struct vmcb_seg es; /* 0x400 */ + struct vmcb_seg cs; /* 0x410 */ + struct vmcb_seg ss; /* 0x420 */ + struct vmcb_seg ds; /* 0x430 */ + struct vmcb_seg fs; /* 0x440 */ + struct vmcb_seg gs; /* 0x450 */ + struct vmcb_seg gdtr; /* 0x460 */ + struct vmcb_seg ldtr; /* 0x470 */ + struct vmcb_seg idtr; /* 0x480 */ + struct vmcb_seg tr; /* 0x490 */ + uint8_t _pad_4a0[0x4cb - 0x4a0]; + uint8_t cpl; /* 0x4CB */ + uint32_t _pad_4cc; + uint64_t efer; /* 0x4D0 */ + uint8_t _pad_4d8[0x548 - 0x4d8]; + uint64_t cr4; /* 0x548 */ + uint64_t cr3; /* 0x550 */ + uint64_t cr0; /* 0x558 */ + uint64_t dr7; /* 0x560 */ + uint64_t dr6; /* 0x568 */ + uint64_t rflags; /* 0x570 */ + uint64_t rip; /* 0x578 */ + uint8_t _pad_580[0x5d8 - 0x580]; + uint64_t rsp; /* 0x5D8 */ + uint8_t _pad_5e0[0x5f8 - 0x5e0]; + uint64_t rax; /* 0x5F8 */ + + uint8_t _pad_tail[0x1000 - 0x600]; +}; + +/* + * Compile-time verification of architectural offsets for the fields used + * by this nested-SVM VMRUN Smoke test. + */ +#define VMCB_CHECK(field, offset) \ + _Static_assert(__builtin_offsetof(struct vmcb, field) == (offset), \ + "VMCB layout mismatch: " #field) +VMCB_CHECK(intercept_insns_vec3, 0x00c); +VMCB_CHECK(intercept_insns_vec4, 0x010); +VMCB_CHECK(asid, 0x058); +VMCB_CHECK(exitcode, 0x070); +VMCB_CHECK(es, 0x400); +VMCB_CHECK(gdtr, 0x460); +VMCB_CHECK(idtr, 0x480); +VMCB_CHECK(tr, 0x490); +VMCB_CHECK(efer, 0x4d0); +VMCB_CHECK(cr4, 0x548); +VMCB_CHECK(cr3, 0x550); +VMCB_CHECK(cr0, 0x558); +VMCB_CHECK(rflags, 0x570); +VMCB_CHECK(rip, 0x578); +VMCB_CHECK(rsp, 0x5d8); +VMCB_CHECK(rax, 0x5f8); +_Static_assert(sizeof(struct vmcb) == 0x1000, "VMCB size != 4 KiB"); +#undef VMCB_CHECK + +/* General Intercept 1 register (offset 0x00C). */ +#define GENERAL1_INTERCEPT_HLT (1u << 24) +#define GENERAL1_INTERCEPT_SHUTDOWN_EVT (1u << 31) + +/* General Intercept 2 register (offset 0x010). */ +#define GENERAL2_INTERCEPT_VMRUN (1u << 0) +#define GENERAL2_INTERCEPT_VMMCALL (1u << 1) + +/* Selected VMEXIT exit codes. */ +#define VMEXIT_HLT 0x078 +#define VMEXIT_SHUTDOWN 0x07f +#define VMEXIT_VMMCALL 0x081 + +#endif /* XTF_NESTED_SVM_VMRUN_VMCB_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */