Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build/common.mk
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
ALL_CATEGORIES := special functional xsa utility in-development
ALL_CATEGORIES := special functional xsa utility in-development nested-svm

ALL_ENVIRONMENTS := pv64 pv32pae hvm64 hvm32pae hvm32pse hvm32

PV_ENVIRONMENTS := $(filter pv%,$(ALL_ENVIRONMENTS))
HVM_ENVIRONMENTS := $(filter hvm%,$(ALL_ENVIRONMENTS))
32BIT_ENVIRONMENTS := $(filter pv32% hvm32%,$(ALL_ENVIRONMENTS))
64BIT_ENVIRONMENTS := $(filter pv64% hvm64%,$(ALL_ENVIRONMENTS))
SVM_ENVIRONMENTS := hvm64

# $(env)_guest => pv or hvm mapping
$(foreach env,$(PV_ENVIRONMENTS),$(eval $(env)_guest := pv))
Expand Down
136 changes: 136 additions & 0 deletions common/nested-svm/setup-l2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#include <nested-svm/setup-l2.h>

/* AMD MSRs. */

/* The VMRUN host-save area */
#define MSR_VM_HSAVE_PA 0xc0010117U

static uint16_t user_desc_vmcb_attr(const user_desc *desc)
{
return desc->type |
(desc->s << 4) |
(desc->dpl << 5) |
(desc->p << 7) |
(desc->limit1 << 8) |
(desc->avl << 12) |
(desc->l << 13) |
(desc->d << 14) |
(desc->g << 15);
}

static bool selector_is_null(uint16_t sel)
{
return !(sel & ~(X86_SEL_TI | X86_SEL_RPL_MASK));
}

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

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

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

if ( !desc->s )
gdt_desc_bytes *= 2;

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

void svm_l1_prepare_for_vmrun(const void *hsave)
{
wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME);
wrmsr(MSR_VM_HSAVE_PA, _u(hsave));
}

void svm_l1_finish_vmrun(void)
{
wrmsr(MSR_EFER, rdmsr(MSR_EFER) & ~EFER_SVME);
}

void svm_l2_build_vmcb(struct vmcb *vmcb, const struct svm_l2_config *cfg)
{
desc_ptr gdt_desc, idt_desc;
const user_desc *gdt;

memset(vmcb, 0, sizeof(*vmcb));

vmcb->intercept_insns_vec3 = cfg->intercept_insns_vec3;
vmcb->intercept_insns_vec4 = cfg->intercept_insns_vec4;
vmcb->intercept_insns_vec5 = cfg->intercept_insns_vec5;
vmcb->asid = cfg->asid ? cfg->asid : 1;

vmcb->cr0 = read_cr0();
vmcb->cr3 = read_cr3();
vmcb->cr4 = read_cr4();
vmcb->efer = cfg->efer;
vmcb->rflags = read_flags();

vmcb->rsp = cfg->rsp;
vmcb->rip = cfg->rip;

sgdt(&gdt_desc);
sidt(&idt_desc);
vmcb->gdtr.base = gdt_desc.base;
vmcb->gdtr.limit = gdt_desc.limit;
vmcb->idtr.base = idt_desc.base;
vmcb->idtr.limit = idt_desc.limit;
gdt = (const user_desc *)gdt_desc.base;

vmcb_set_seg_desc(&vmcb->ldtr, gdt, gdt_desc.limit, sldt());
vmcb_set_seg_desc(&vmcb->tr, gdt, gdt_desc.limit, str());

vmcb->cs.sel = __KERN_CS;
vmcb->cs.attr = 0xa9b;
vmcb->cs.limit = ~0u;

vmcb->ds.sel = __USER_DS;
vmcb->ds.attr = 0xcf3;
vmcb->ds.limit = ~0u;
vmcb->es = vmcb->fs = vmcb->gs = vmcb->ds;

vmcb->ss.sel = __KERN_DS;
vmcb->ss.attr = 0;
vmcb->ss.limit = 0;
}

/*
* Local variables:
* mode: C
* c-file-style: "BSD"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: nil
* End:
*/
104 changes: 104 additions & 0 deletions common/nested-svm/test-harness.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#include <nested-svm/test-harness.h>

static uint64_t svm_negative_paddr(const void *vmcb_page,
enum svm_negative_paddr_kind kind)
{
switch ( kind )
{
case SVM_NEGATIVE_PADDR_ALIGNED:
return _u(vmcb_page);

case SVM_NEGATIVE_PADDR_UNALIGNED:
return _u(vmcb_page) | 1ul;

case SVM_NEGATIVE_PADDR_TOO_WIDE:
return 1ull << maxphysaddr;
}

unreachable();
}

/* set or clear EFER.SVME and return the original EFER value */
static uint64_t update_efer_svme(bool enable)
{
uint64_t efer = rdmsr(MSR_EFER);
uint64_t new_efer = enable ? (efer | EFER_SVME) : (efer & ~EFER_SVME);

wrmsr(MSR_EFER, new_efer);

return efer;
}

static bool svm_negative_run(const struct svm_negative_case *t,
const struct svm_negative_runner *runner,
exinfo_t *res)
{
uint64_t paddr = svm_negative_paddr(runner->vmcb_page, t->paddr_kind);
uint64_t orig_efer = 0;

if ( runner->setup && !runner->setup(t, paddr, runner->data) )
return false;

if ( runner->manage_host_svme )
orig_efer = update_efer_svme(t->svme);

*res = 0;

switch ( t->mode )
{
case SVM_NEGATIVE_KERNEL:
*res = runner->ops->kernel(paddr);
break;

case SVM_NEGATIVE_USER:
*res = exec_user_param(runner->ops->user, paddr);
break;
}

if ( runner->manage_host_svme )
wrmsr(MSR_EFER, orig_efer);

if ( runner->teardown )
runner->teardown(t, paddr, runner->data);

return true;
}

static void svm_negative_check_case(const struct svm_negative_case *t,
const struct svm_negative_runner *runner)
{
exinfo_t got;

if ( !svm_negative_run(t, runner, &got) )
return;

if ( got == t->expected )
return;

xtf_failure("Fail: %s, got %pe, expected %pe\n",
t->name, _p(got), _p(t->expected));
}

bool svm_negative_supported(void)
{
if ( !cpu_has_svm )
{
xtf_skip("Skip: SVM not available\n");
return false;
}

if ( !vendor_is_amd )
xtf_warning("Warning: SVM found on non-AMD processor\n");

return true;
}

void svm_negative_check_cases(const struct svm_negative_case *cases,
unsigned int nr_cases,
const struct svm_negative_runner *runner)
{
unsigned int i;

for ( i = 0; i < nr_cases; ++i )
svm_negative_check_case(&cases[i], runner);
}
14 changes: 14 additions & 0 deletions common/nested-svm/vmrun-shared-minimal.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Shared SVM nested-virt VMRUN trampoline for nested-SVM tests.
*/
#include <xtf/asm_macros.h>

ENTRY(svm_vmrun)
mov %rdi, %rax
vmload %rax
stgi
vmrun %rax
vmsave %rax
stgi
ret
ENDFUNC(svm_vmrun)
6 changes: 5 additions & 1 deletion docs/all-tests.dox
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ states.

@subpage test-debug-regs - Debugging facility tests.

@subpage test-nested-svm - Nested SVM tests.
@subpage test-nested-svm-vmrun - Nested SVM VMRUN tests.

@subpage test-nested-svm-vmload-negative - Negative Nested SVM VMLOAD tests.

@subpage test-nested-svm-vmsave-negative - Negative Nested SVM VMSAVE tests.

@subpage test-nested-vmx - Nested VT-x tests.
*/
31 changes: 31 additions & 0 deletions include/nested-svm/setup-l2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef XTF_NESTED_SVM_SETUP_L2_H
#define XTF_NESTED_SVM_SETUP_L2_H

#include <xtf.h>

#include "vmcb.h"

/* Minimal caller-supplied state for building an L2 VMCB from L1. */
struct svm_l2_config {
unsigned long rip;
unsigned long rsp;
uint32_t asid;
uint32_t intercept_insns_vec3;
uint32_t intercept_insns_vec4;
uint32_t intercept_insns_vec5;
uint64_t efer;
};

/* Enable SVM in L1 and program the host-save area used by VMRUN. */
void svm_l1_prepare_for_vmrun(const void *hsave);

/* Leave L1 out of nested-hypervisor mode before guest shutdown. */
void svm_l1_finish_vmrun(void);

/* Build a minimal long-mode L2 VMCB that reuses the current L1 environment. */
void svm_l2_build_vmcb(struct vmcb *vmcb, const struct svm_l2_config *cfg);

/* Enter an L2 guest via the shared VMLOAD/VMRUN/VMSAVE trampoline. */
void svm_vmrun(unsigned long l2_vmcb_pa);

#endif /* XTF_TESTS_NESTED_SVM_L2_H */
63 changes: 63 additions & 0 deletions include/nested-svm/test-harness.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#ifndef XTF_NESTED_SVM_TEST_HARNESS_H
#define XTF_NESTED_SVM_TEST_HARNESS_H

#include <xtf.h>

enum svm_negative_mode {
SVM_NEGATIVE_KERNEL,
SVM_NEGATIVE_USER,
};

enum svm_negative_paddr_kind {
SVM_NEGATIVE_PADDR_ALIGNED,
SVM_NEGATIVE_PADDR_UNALIGNED,
SVM_NEGATIVE_PADDR_TOO_WIDE,
};

struct svm_negative_case {
const char *name;
bool svme;
enum svm_negative_mode mode;
enum svm_negative_paddr_kind paddr_kind;
exinfo_t expected;
};

struct svm_negative_ops {
exinfo_t (*kernel)(uint64_t paddr);
unsigned long (*user)(unsigned long paddr);
};

/* Return false when setup already reported a skip or failure for the case. */
typedef bool (*svm_negative_setup_fn)(const struct svm_negative_case *t,
uint64_t paddr, void *data);
typedef void (*svm_negative_teardown_fn)(const struct svm_negative_case *t,
uint64_t paddr, void *data);

/*
* Shared execution context for a family of SVM negative cases.
*
* Optional setup/teardown hooks let higher-level tests prepare nested state
* such as L2 or L3 control structures outside the fixed case matrix before
* the helper dispatches the actual instruction. L1-style callers can leave
* manage_host_svme enabled so the helper toggles live EFER.SVME itself,
* while nested callers can keep host SVME fixed and use setup/teardown to
* manage guest-visible SVME state explicitly.
*/
struct svm_negative_runner {
const void *vmcb_page;
const struct svm_negative_ops *ops;
bool manage_host_svme;
void *data;
svm_negative_setup_fn setup;
svm_negative_teardown_fn teardown;
};

/* Check basic nested-SVM availability in the current environment. */
bool svm_negative_supported(void);

/* Execute and check the supplied case matrix with the shared runner. */
void svm_negative_check_cases(const struct svm_negative_case *cases,
unsigned int nr_cases,
const struct svm_negative_runner *runner);

#endif /* TESTS_NESTED_SVM_SVM_NEGATIVE_H */
Loading
Loading