Merge tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm
Pull kvm updates from Paolo Bonzini:
"ARM:
- Proper emulation of the OSLock feature of the debug architecture
- Scalibility improvements for the MMU lock when dirty logging is on
- New VMID allocator, which will eventually help with SVA in VMs
- Better support for PMUs in heterogenous systems
- PSCI 1.1 support, enabling support for SYSTEM_RESET2
- Implement CONFIG_DEBUG_LIST at EL2
- Make CONFIG_ARM64_ERRATUM_2077057 default y
- Reduce the overhead of VM exit when no interrupt is pending
- Remove traces of 32bit ARM host support from the documentation
- Updated vgic selftests
- Various cleanups, doc updates and spelling fixes
RISC-V:
- Prevent KVM_COMPAT from being selected
- Optimize __kvm_riscv_switch_to() implementation
- RISC-V SBI v0.3 support
s390:
- memop selftest
- fix SCK locking
- adapter interruptions virtualization for secure guests
- add Claudio Imbrenda as maintainer
- first step to do proper storage key checking
x86:
- Continue switching kvm_x86_ops to static_call(); introduce
static_call_cond() and __static_call_ret0 when applicable.
- Cleanup unused arguments in several functions
- Synthesize AMD 0x80000021 leaf
- Fixes and optimization for Hyper-V sparse-bank hypercalls
- Implement Hyper-V's enlightened MSR bitmap for nested SVM
- Remove MMU auditing
- Eager splitting of page tables (new aka "TDP" MMU only) when dirty
page tracking is enabled
- Cleanup the implementation of the guest PGD cache
- Preparation for the implementation of Intel IPI virtualization
- Fix some segment descriptor checks in the emulator
- Allow AMD AVIC support on systems with physical APIC ID above 255
- Better API to disable virtualization quirks
- Fixes and optimizations for the zapping of page tables:
- Zap roots in two passes, avoiding RCU read-side critical
sections that last too long for very large guests backed by 4
KiB SPTEs.
- Zap invalid and defunct roots asynchronously via
concurrency-managed work queue.
- Allowing yielding when zapping TDP MMU roots in response to the
root's last reference being put.
- Batch more TLB flushes with an RCU trick. Whoever frees the
paging structure now holds RCU as a proxy for all vCPUs running
in the guest, i.e. to prolongs the grace period on their behalf.
It then kicks the the vCPUs out of guest mode before doing
rcu_read_unlock().
Generic:
- Introduce __vcalloc and use it for very large allocations that need
memcg accounting"
* tag 'for-linus' of git://git.kernel.org/pub/scm/virt/kvm/kvm: (246 commits)
KVM: use kvcalloc for array allocations
KVM: x86: Introduce KVM_CAP_DISABLE_QUIRKS2
kvm: x86: Require const tsc for RT
KVM: x86: synthesize CPUID leaf 0x80000021h if useful
KVM: x86: add support for CPUID leaf 0x80000021
KVM: x86: do not use KVM_X86_OP_OPTIONAL_RET0 for get_mt_mask
Revert "KVM: x86/mmu: Zap only TDP MMU leafs in kvm_zap_gfn_range()"
kvm: x86/mmu: Flush TLB before zap_gfn_range releases RCU
KVM: arm64: fix typos in comments
KVM: arm64: Generalise VM features into a set of flags
KVM: s390: selftests: Add error memop tests
KVM: s390: selftests: Add more copy memop tests
KVM: s390: selftests: Add named stages for memop test
KVM: s390: selftests: Add macro as abstraction for MEM_OP
KVM: s390: selftests: Split memop tests
KVM: s390x: fix SCK locking
RISC-V: KVM: Implement SBI HSM suspend call
RISC-V: KVM: Add common kvm_riscv_vcpu_wfi() function
RISC-V: Add SBI HSM suspend related defines
RISC-V: KVM: Implement SBI v0.3 SRST extension
...
This commit is contained in:
3
tools/testing/selftests/kvm/.gitignore
vendored
3
tools/testing/selftests/kvm/.gitignore
vendored
@@ -8,6 +8,7 @@
|
||||
/s390x/memop
|
||||
/s390x/resets
|
||||
/s390x/sync_regs_test
|
||||
/s390x/tprot
|
||||
/x86_64/amx_test
|
||||
/x86_64/cpuid_test
|
||||
/x86_64/cr4_cpuid_sync_test
|
||||
@@ -46,6 +47,7 @@
|
||||
/x86_64/vmx_tsc_adjust_test
|
||||
/x86_64/vmx_nested_tsc_scaling_test
|
||||
/x86_64/xapic_ipi_test
|
||||
/x86_64/xapic_state_test
|
||||
/x86_64/xen_shinfo_test
|
||||
/x86_64/xen_vmcall_test
|
||||
/x86_64/xss_msr_test
|
||||
@@ -57,6 +59,7 @@
|
||||
/hardware_disable_test
|
||||
/kvm_create_max_vcpus
|
||||
/kvm_page_table_test
|
||||
/max_guest_memory_test
|
||||
/memslot_modification_stress_test
|
||||
/memslot_perf_test
|
||||
/rseq_test
|
||||
|
||||
@@ -51,6 +51,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/emulator_error_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_clock
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_cpuid
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_features
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/hyperv_svm_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/kvm_clock_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/kvm_pv_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/mmio_warning_test
|
||||
@@ -76,6 +77,7 @@ TEST_GEN_PROGS_x86_64 += x86_64/vmx_set_nested_state_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/vmx_tsc_adjust_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/vmx_nested_tsc_scaling_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/xapic_ipi_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/xapic_state_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/xss_msr_test
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/debug_regs
|
||||
TEST_GEN_PROGS_x86_64 += x86_64/tsc_msrs_test
|
||||
@@ -91,6 +93,7 @@ TEST_GEN_PROGS_x86_64 += dirty_log_perf_test
|
||||
TEST_GEN_PROGS_x86_64 += hardware_disable_test
|
||||
TEST_GEN_PROGS_x86_64 += kvm_create_max_vcpus
|
||||
TEST_GEN_PROGS_x86_64 += kvm_page_table_test
|
||||
TEST_GEN_PROGS_x86_64 += max_guest_memory_test
|
||||
TEST_GEN_PROGS_x86_64 += memslot_modification_stress_test
|
||||
TEST_GEN_PROGS_x86_64 += memslot_perf_test
|
||||
TEST_GEN_PROGS_x86_64 += rseq_test
|
||||
@@ -120,6 +123,7 @@ TEST_GEN_PROGS_aarch64 += kvm_binary_stats_test
|
||||
TEST_GEN_PROGS_s390x = s390x/memop
|
||||
TEST_GEN_PROGS_s390x += s390x/resets
|
||||
TEST_GEN_PROGS_s390x += s390x/sync_regs_test
|
||||
TEST_GEN_PROGS_s390x += s390x/tprot
|
||||
TEST_GEN_PROGS_s390x += demand_paging_test
|
||||
TEST_GEN_PROGS_s390x += dirty_log_test
|
||||
TEST_GEN_PROGS_s390x += kvm_create_max_vcpus
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#define SPSR_D (1 << 9)
|
||||
#define SPSR_SS (1 << 21)
|
||||
|
||||
extern unsigned char sw_bp, hw_bp, bp_svc, bp_brk, hw_wp, ss_start;
|
||||
extern unsigned char sw_bp, sw_bp2, hw_bp, hw_bp2, bp_svc, bp_brk, hw_wp, ss_start;
|
||||
static volatile uint64_t sw_bp_addr, hw_bp_addr;
|
||||
static volatile uint64_t wp_addr, wp_data_addr;
|
||||
static volatile uint64_t svc_addr;
|
||||
@@ -47,6 +47,14 @@ static void reset_debug_state(void)
|
||||
isb();
|
||||
}
|
||||
|
||||
static void enable_os_lock(void)
|
||||
{
|
||||
write_sysreg(1, oslar_el1);
|
||||
isb();
|
||||
|
||||
GUEST_ASSERT(read_sysreg(oslsr_el1) & 2);
|
||||
}
|
||||
|
||||
static void install_wp(uint64_t addr)
|
||||
{
|
||||
uint32_t wcr;
|
||||
@@ -99,6 +107,7 @@ static void guest_code(void)
|
||||
GUEST_SYNC(0);
|
||||
|
||||
/* Software-breakpoint */
|
||||
reset_debug_state();
|
||||
asm volatile("sw_bp: brk #0");
|
||||
GUEST_ASSERT_EQ(sw_bp_addr, PC(sw_bp));
|
||||
|
||||
@@ -152,6 +161,51 @@ static void guest_code(void)
|
||||
GUEST_ASSERT_EQ(ss_addr[1], PC(ss_start) + 4);
|
||||
GUEST_ASSERT_EQ(ss_addr[2], PC(ss_start) + 8);
|
||||
|
||||
GUEST_SYNC(6);
|
||||
|
||||
/* OS Lock does not block software-breakpoint */
|
||||
reset_debug_state();
|
||||
enable_os_lock();
|
||||
sw_bp_addr = 0;
|
||||
asm volatile("sw_bp2: brk #0");
|
||||
GUEST_ASSERT_EQ(sw_bp_addr, PC(sw_bp2));
|
||||
|
||||
GUEST_SYNC(7);
|
||||
|
||||
/* OS Lock blocking hardware-breakpoint */
|
||||
reset_debug_state();
|
||||
enable_os_lock();
|
||||
install_hw_bp(PC(hw_bp2));
|
||||
hw_bp_addr = 0;
|
||||
asm volatile("hw_bp2: nop");
|
||||
GUEST_ASSERT_EQ(hw_bp_addr, 0);
|
||||
|
||||
GUEST_SYNC(8);
|
||||
|
||||
/* OS Lock blocking watchpoint */
|
||||
reset_debug_state();
|
||||
enable_os_lock();
|
||||
write_data = '\0';
|
||||
wp_data_addr = 0;
|
||||
install_wp(PC(write_data));
|
||||
write_data = 'x';
|
||||
GUEST_ASSERT_EQ(write_data, 'x');
|
||||
GUEST_ASSERT_EQ(wp_data_addr, 0);
|
||||
|
||||
GUEST_SYNC(9);
|
||||
|
||||
/* OS Lock blocking single-step */
|
||||
reset_debug_state();
|
||||
enable_os_lock();
|
||||
ss_addr[0] = 0;
|
||||
install_ss();
|
||||
ss_idx = 0;
|
||||
asm volatile("mrs x0, esr_el1\n\t"
|
||||
"add x0, x0, #1\n\t"
|
||||
"msr daifset, #8\n\t"
|
||||
: : : "x0");
|
||||
GUEST_ASSERT_EQ(ss_addr[0], 0);
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
@@ -223,7 +277,7 @@ int main(int argc, char *argv[])
|
||||
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
|
||||
ESR_EC_SVC64, guest_svc_handler);
|
||||
|
||||
for (stage = 0; stage < 7; stage++) {
|
||||
for (stage = 0; stage < 11; stage++) {
|
||||
vcpu_run(vm, VCPU_ID);
|
||||
|
||||
switch (get_ucall(vm, VCPU_ID, &uc)) {
|
||||
|
||||
@@ -760,6 +760,7 @@ static __u64 base_regs[] = {
|
||||
ARM64_SYS_REG(2, 0, 0, 15, 5),
|
||||
ARM64_SYS_REG(2, 0, 0, 15, 6),
|
||||
ARM64_SYS_REG(2, 0, 0, 15, 7),
|
||||
ARM64_SYS_REG(2, 0, 1, 1, 4), /* OSLSR_EL1 */
|
||||
ARM64_SYS_REG(2, 4, 0, 7, 0), /* DBGVCR32_EL2 */
|
||||
ARM64_SYS_REG(3, 0, 0, 0, 5), /* MPIDR_EL1 */
|
||||
ARM64_SYS_REG(3, 0, 0, 1, 0), /* ID_PFR0_EL1 */
|
||||
|
||||
@@ -306,7 +306,8 @@ static void guest_restore_active(struct test_args *args,
|
||||
uint32_t prio, intid, ap1r;
|
||||
int i;
|
||||
|
||||
/* Set the priorities of the first (KVM_NUM_PRIOS - 1) IRQs
|
||||
/*
|
||||
* Set the priorities of the first (KVM_NUM_PRIOS - 1) IRQs
|
||||
* in descending order, so intid+1 can preempt intid.
|
||||
*/
|
||||
for (i = 0, prio = (num - 1) * 8; i < num; i++, prio -= 8) {
|
||||
@@ -315,7 +316,8 @@ static void guest_restore_active(struct test_args *args,
|
||||
gic_set_priority(intid, prio);
|
||||
}
|
||||
|
||||
/* In a real migration, KVM would restore all GIC state before running
|
||||
/*
|
||||
* In a real migration, KVM would restore all GIC state before running
|
||||
* guest code.
|
||||
*/
|
||||
for (i = 0; i < num; i++) {
|
||||
@@ -472,10 +474,10 @@ static void test_restore_active(struct test_args *args, struct kvm_inject_desc *
|
||||
guest_restore_active(args, MIN_SPI, 4, f->cmd);
|
||||
}
|
||||
|
||||
static void guest_code(struct test_args args)
|
||||
static void guest_code(struct test_args *args)
|
||||
{
|
||||
uint32_t i, nr_irqs = args.nr_irqs;
|
||||
bool level_sensitive = args.level_sensitive;
|
||||
uint32_t i, nr_irqs = args->nr_irqs;
|
||||
bool level_sensitive = args->level_sensitive;
|
||||
struct kvm_inject_desc *f, *inject_fns;
|
||||
|
||||
gic_init(GIC_V3, 1, dist, redist);
|
||||
@@ -484,11 +486,11 @@ static void guest_code(struct test_args args)
|
||||
gic_irq_enable(i);
|
||||
|
||||
for (i = MIN_SPI; i < nr_irqs; i++)
|
||||
gic_irq_set_config(i, !args.level_sensitive);
|
||||
gic_irq_set_config(i, !level_sensitive);
|
||||
|
||||
gic_set_eoi_split(args.eoi_split);
|
||||
gic_set_eoi_split(args->eoi_split);
|
||||
|
||||
reset_priorities(&args);
|
||||
reset_priorities(args);
|
||||
gic_set_priority_mask(CPU_PRIO_MASK);
|
||||
|
||||
inject_fns = level_sensitive ? inject_level_fns
|
||||
@@ -497,17 +499,18 @@ static void guest_code(struct test_args args)
|
||||
local_irq_enable();
|
||||
|
||||
/* Start the tests. */
|
||||
for_each_supported_inject_fn(&args, inject_fns, f) {
|
||||
test_injection(&args, f);
|
||||
test_preemption(&args, f);
|
||||
test_injection_failure(&args, f);
|
||||
for_each_supported_inject_fn(args, inject_fns, f) {
|
||||
test_injection(args, f);
|
||||
test_preemption(args, f);
|
||||
test_injection_failure(args, f);
|
||||
}
|
||||
|
||||
/* Restore the active state of IRQs. This would happen when live
|
||||
/*
|
||||
* Restore the active state of IRQs. This would happen when live
|
||||
* migrating IRQs in the middle of being handled.
|
||||
*/
|
||||
for_each_supported_activate_fn(&args, set_active_fns, f)
|
||||
test_restore_active(&args, f);
|
||||
for_each_supported_activate_fn(args, set_active_fns, f)
|
||||
test_restore_active(args, f);
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
@@ -573,8 +576,8 @@ static void kvm_set_gsi_routing_irqchip_check(struct kvm_vm *vm,
|
||||
kvm_gsi_routing_write(vm, routing);
|
||||
} else {
|
||||
ret = _kvm_gsi_routing_write(vm, routing);
|
||||
/* The kernel only checks for KVM_IRQCHIP_NUM_PINS. */
|
||||
if (intid >= KVM_IRQCHIP_NUM_PINS)
|
||||
/* The kernel only checks e->irqchip.pin >= KVM_IRQCHIP_NUM_PINS */
|
||||
if (((uint64_t)intid + num - 1 - MIN_SPI) >= KVM_IRQCHIP_NUM_PINS)
|
||||
TEST_ASSERT(ret != 0 && errno == EINVAL,
|
||||
"Bad intid %u did not cause KVM_SET_GSI_ROUTING "
|
||||
"error: rc: %i errno: %i", intid, ret, errno);
|
||||
@@ -739,6 +742,7 @@ static void test_vgic(uint32_t nr_irqs, bool level_sensitive, bool eoi_split)
|
||||
int gic_fd;
|
||||
struct kvm_vm *vm;
|
||||
struct kvm_inject_args inject_args;
|
||||
vm_vaddr_t args_gva;
|
||||
|
||||
struct test_args args = {
|
||||
.nr_irqs = nr_irqs,
|
||||
@@ -757,7 +761,9 @@ static void test_vgic(uint32_t nr_irqs, bool level_sensitive, bool eoi_split)
|
||||
vcpu_init_descriptor_tables(vm, VCPU_ID);
|
||||
|
||||
/* Setup the guest args page (so it gets the args). */
|
||||
vcpu_args_set(vm, 0, 1, args);
|
||||
args_gva = vm_vaddr_alloc_page(vm);
|
||||
memcpy(addr_gva2hva(vm, args_gva), &args, sizeof(args));
|
||||
vcpu_args_set(vm, 0, 1, args_gva);
|
||||
|
||||
gic_fd = vgic_v3_setup(vm, 1, nr_irqs,
|
||||
GICD_BASE_GPA, GICR_BASE_GPA);
|
||||
@@ -841,7 +847,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
/* If the user just specified nr_irqs and/or gic_version, then run all
|
||||
/*
|
||||
* If the user just specified nr_irqs and/or gic_version, then run all
|
||||
* combinations.
|
||||
*/
|
||||
if (default_args) {
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
#include "test_util.h"
|
||||
#include "perf_test_util.h"
|
||||
#include "guest_modes.h"
|
||||
#ifdef __aarch64__
|
||||
#include "aarch64/vgic.h"
|
||||
|
||||
#define GICD_BASE_GPA 0x8000000ULL
|
||||
#define GICR_BASE_GPA 0x80A0000ULL
|
||||
#endif
|
||||
|
||||
/* How many host loops to run by default (one KVM_GET_DIRTY_LOG for each loop)*/
|
||||
#define TEST_HOST_LOOP_N 2UL
|
||||
@@ -200,6 +206,10 @@ static void run_test(enum vm_guest_mode mode, void *arg)
|
||||
vm_enable_cap(vm, &cap);
|
||||
}
|
||||
|
||||
#ifdef __aarch64__
|
||||
vgic_v3_setup(vm, nr_vcpus, 64, GICD_BASE_GPA, GICR_BASE_GPA);
|
||||
#endif
|
||||
|
||||
/* Start the iterations */
|
||||
iteration = 0;
|
||||
host_quit = false;
|
||||
@@ -298,12 +308,18 @@ static void run_test(enum vm_guest_mode mode, void *arg)
|
||||
static void help(char *name)
|
||||
{
|
||||
puts("");
|
||||
printf("usage: %s [-h] [-i iterations] [-p offset] "
|
||||
printf("usage: %s [-h] [-i iterations] [-p offset] [-g]"
|
||||
"[-m mode] [-b vcpu bytes] [-v vcpus] [-o] [-s mem type]"
|
||||
"[-x memslots]\n", name);
|
||||
puts("");
|
||||
printf(" -i: specify iteration counts (default: %"PRIu64")\n",
|
||||
TEST_HOST_LOOP_N);
|
||||
printf(" -g: Do not enable KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2. This\n"
|
||||
" makes KVM_GET_DIRTY_LOG clear the dirty log (i.e.\n"
|
||||
" KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE is not enabled)\n"
|
||||
" and writes will be tracked as soon as dirty logging is\n"
|
||||
" enabled on the memslot (i.e. KVM_DIRTY_LOG_INITIALLY_SET\n"
|
||||
" is not enabled).\n");
|
||||
printf(" -p: specify guest physical test memory offset\n"
|
||||
" Warning: a low offset can conflict with the loaded test code.\n");
|
||||
guest_modes_help();
|
||||
@@ -343,8 +359,11 @@ int main(int argc, char *argv[])
|
||||
|
||||
guest_modes_append_default();
|
||||
|
||||
while ((opt = getopt(argc, argv, "hi:p:m:b:f:v:os:x:")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "ghi:p:m:b:f:v:os:x:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'g':
|
||||
dirty_log_manual_caps = 0;
|
||||
break;
|
||||
case 'i':
|
||||
p.iterations = atoi(optarg);
|
||||
break;
|
||||
|
||||
@@ -123,6 +123,7 @@ int kvm_memcmp_hva_gva(void *hva, struct kvm_vm *vm, const vm_vaddr_t gva,
|
||||
size_t len);
|
||||
|
||||
void kvm_vm_elf_load(struct kvm_vm *vm, const char *filename);
|
||||
int kvm_memfd_alloc(size_t size, bool hugepages);
|
||||
|
||||
void vm_dump(FILE *stream, struct kvm_vm *vm, uint8_t indent);
|
||||
|
||||
@@ -147,6 +148,10 @@ void vcpu_dump(FILE *stream, struct kvm_vm *vm, uint32_t vcpuid,
|
||||
|
||||
void vm_create_irqchip(struct kvm_vm *vm);
|
||||
|
||||
void vm_set_user_memory_region(struct kvm_vm *vm, uint32_t slot, uint32_t flags,
|
||||
uint64_t gpa, uint64_t size, void *hva);
|
||||
int __vm_set_user_memory_region(struct kvm_vm *vm, uint32_t slot, uint32_t flags,
|
||||
uint64_t gpa, uint64_t size, void *hva);
|
||||
void vm_userspace_mem_region_add(struct kvm_vm *vm,
|
||||
enum vm_mem_backing_src_type src_type,
|
||||
uint64_t guest_paddr, uint32_t slot, uint64_t npages,
|
||||
@@ -336,6 +341,9 @@ struct kvm_vm *vm_create_with_vcpus(enum vm_guest_mode mode, uint32_t nr_vcpus,
|
||||
uint32_t num_percpu_pages, void *guest_code,
|
||||
uint32_t vcpuids[]);
|
||||
|
||||
/* Create a default VM without any vcpus. */
|
||||
struct kvm_vm *vm_create_without_vcpus(enum vm_guest_mode mode, uint64_t pages);
|
||||
|
||||
/*
|
||||
* Adds a vCPU with reasonable defaults (e.g. a stack)
|
||||
*
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef SELFTEST_KVM_PROCESSOR_H
|
||||
#define SELFTEST_KVM_PROCESSOR_H
|
||||
|
||||
#include <linux/compiler.h>
|
||||
|
||||
/* Bits in the region/segment table entry */
|
||||
#define REGION_ENTRY_ORIGIN ~0xfffUL /* region/segment table origin */
|
||||
#define REGION_ENTRY_PROTECT 0x200 /* region protection bit */
|
||||
@@ -19,4 +21,10 @@
|
||||
#define PAGE_PROTECT 0x200 /* HW read-only bit */
|
||||
#define PAGE_NOEXEC 0x100 /* HW no-execute bit */
|
||||
|
||||
/* Is there a portable way to do this? */
|
||||
static inline void cpu_relax(void)
|
||||
{
|
||||
barrier();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#define APIC_SPIV 0xF0
|
||||
#define APIC_SPIV_FOCUS_DISABLED (1 << 9)
|
||||
#define APIC_SPIV_APIC_ENABLED (1 << 8)
|
||||
#define APIC_IRR 0x200
|
||||
#define APIC_ICR 0x300
|
||||
#define APIC_DEST_SELF 0x40000
|
||||
#define APIC_DEST_ALLINC 0x80000
|
||||
|
||||
@@ -213,6 +213,25 @@ struct hv_enlightened_vmcs {
|
||||
u64 padding64_6[7];
|
||||
};
|
||||
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE 0
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_IO_BITMAP BIT(0)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP BIT(1)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP2 BIT(2)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP1 BIT(3)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_PROC BIT(4)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_EVENT BIT(5)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_ENTRY BIT(6)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_EXCPN BIT(7)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR BIT(8)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_XLAT BIT(9)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_BASIC BIT(10)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1 BIT(11)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2 BIT(12)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER BIT(13)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1 BIT(14)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_ENLIGHTENMENTSCONTROL BIT(15)
|
||||
#define HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL 0xFFFF
|
||||
|
||||
#define HV_X64_MSR_VP_ASSIST_PAGE 0x40000073
|
||||
#define HV_X64_MSR_VP_ASSIST_PAGE_ENABLE 0x00000001
|
||||
#define HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT 12
|
||||
@@ -648,381 +667,507 @@ static inline int evmcs_vmwrite(uint64_t encoding, uint64_t value)
|
||||
switch (encoding) {
|
||||
case GUEST_RIP:
|
||||
current_evmcs->guest_rip = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case GUEST_RSP:
|
||||
current_evmcs->guest_rsp = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_BASIC;
|
||||
break;
|
||||
case GUEST_RFLAGS:
|
||||
current_evmcs->guest_rflags = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_BASIC;
|
||||
break;
|
||||
case HOST_IA32_PAT:
|
||||
current_evmcs->host_ia32_pat = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_IA32_EFER:
|
||||
current_evmcs->host_ia32_efer = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_CR0:
|
||||
current_evmcs->host_cr0 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_CR3:
|
||||
current_evmcs->host_cr3 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_CR4:
|
||||
current_evmcs->host_cr4 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_IA32_SYSENTER_ESP:
|
||||
current_evmcs->host_ia32_sysenter_esp = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_IA32_SYSENTER_EIP:
|
||||
current_evmcs->host_ia32_sysenter_eip = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_RIP:
|
||||
current_evmcs->host_rip = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case IO_BITMAP_A:
|
||||
current_evmcs->io_bitmap_a = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_IO_BITMAP;
|
||||
break;
|
||||
case IO_BITMAP_B:
|
||||
current_evmcs->io_bitmap_b = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_IO_BITMAP;
|
||||
break;
|
||||
case MSR_BITMAP:
|
||||
current_evmcs->msr_bitmap = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP;
|
||||
break;
|
||||
case GUEST_ES_BASE:
|
||||
current_evmcs->guest_es_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_CS_BASE:
|
||||
current_evmcs->guest_cs_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_SS_BASE:
|
||||
current_evmcs->guest_ss_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_DS_BASE:
|
||||
current_evmcs->guest_ds_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_FS_BASE:
|
||||
current_evmcs->guest_fs_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_GS_BASE:
|
||||
current_evmcs->guest_gs_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_LDTR_BASE:
|
||||
current_evmcs->guest_ldtr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_TR_BASE:
|
||||
current_evmcs->guest_tr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_GDTR_BASE:
|
||||
current_evmcs->guest_gdtr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_IDTR_BASE:
|
||||
current_evmcs->guest_idtr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case TSC_OFFSET:
|
||||
current_evmcs->tsc_offset = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP2;
|
||||
break;
|
||||
case VIRTUAL_APIC_PAGE_ADDR:
|
||||
current_evmcs->virtual_apic_page_addr = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP2;
|
||||
break;
|
||||
case VMCS_LINK_POINTER:
|
||||
current_evmcs->vmcs_link_pointer = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_IA32_DEBUGCTL:
|
||||
current_evmcs->guest_ia32_debugctl = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_IA32_PAT:
|
||||
current_evmcs->guest_ia32_pat = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_IA32_EFER:
|
||||
current_evmcs->guest_ia32_efer = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_PDPTR0:
|
||||
current_evmcs->guest_pdptr0 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_PDPTR1:
|
||||
current_evmcs->guest_pdptr1 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_PDPTR2:
|
||||
current_evmcs->guest_pdptr2 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_PDPTR3:
|
||||
current_evmcs->guest_pdptr3 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_PENDING_DBG_EXCEPTIONS:
|
||||
current_evmcs->guest_pending_dbg_exceptions = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_SYSENTER_ESP:
|
||||
current_evmcs->guest_sysenter_esp = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_SYSENTER_EIP:
|
||||
current_evmcs->guest_sysenter_eip = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case CR0_GUEST_HOST_MASK:
|
||||
current_evmcs->cr0_guest_host_mask = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case CR4_GUEST_HOST_MASK:
|
||||
current_evmcs->cr4_guest_host_mask = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case CR0_READ_SHADOW:
|
||||
current_evmcs->cr0_read_shadow = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case CR4_READ_SHADOW:
|
||||
current_evmcs->cr4_read_shadow = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case GUEST_CR0:
|
||||
current_evmcs->guest_cr0 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case GUEST_CR3:
|
||||
current_evmcs->guest_cr3 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case GUEST_CR4:
|
||||
current_evmcs->guest_cr4 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case GUEST_DR7:
|
||||
current_evmcs->guest_dr7 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CRDR;
|
||||
break;
|
||||
case HOST_FS_BASE:
|
||||
current_evmcs->host_fs_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
break;
|
||||
case HOST_GS_BASE:
|
||||
current_evmcs->host_gs_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
break;
|
||||
case HOST_TR_BASE:
|
||||
current_evmcs->host_tr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
break;
|
||||
case HOST_GDTR_BASE:
|
||||
current_evmcs->host_gdtr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
break;
|
||||
case HOST_IDTR_BASE:
|
||||
current_evmcs->host_idtr_base = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
break;
|
||||
case HOST_RSP:
|
||||
current_evmcs->host_rsp = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
break;
|
||||
case EPT_POINTER:
|
||||
current_evmcs->ept_pointer = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_XLAT;
|
||||
break;
|
||||
case GUEST_BNDCFGS:
|
||||
current_evmcs->guest_bndcfgs = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case XSS_EXIT_BITMAP:
|
||||
current_evmcs->xss_exit_bitmap = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP2;
|
||||
break;
|
||||
case GUEST_PHYSICAL_ADDRESS:
|
||||
current_evmcs->guest_physical_address = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case EXIT_QUALIFICATION:
|
||||
current_evmcs->exit_qualification = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case GUEST_LINEAR_ADDRESS:
|
||||
current_evmcs->guest_linear_address = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case VM_EXIT_MSR_STORE_ADDR:
|
||||
current_evmcs->vm_exit_msr_store_addr = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case VM_EXIT_MSR_LOAD_ADDR:
|
||||
current_evmcs->vm_exit_msr_load_addr = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case VM_ENTRY_MSR_LOAD_ADDR:
|
||||
current_evmcs->vm_entry_msr_load_addr = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case CR3_TARGET_VALUE0:
|
||||
current_evmcs->cr3_target_value0 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case CR3_TARGET_VALUE1:
|
||||
current_evmcs->cr3_target_value1 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case CR3_TARGET_VALUE2:
|
||||
current_evmcs->cr3_target_value2 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case CR3_TARGET_VALUE3:
|
||||
current_evmcs->cr3_target_value3 = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case TPR_THRESHOLD:
|
||||
current_evmcs->tpr_threshold = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case GUEST_INTERRUPTIBILITY_INFO:
|
||||
current_evmcs->guest_interruptibility_info = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_BASIC;
|
||||
break;
|
||||
case CPU_BASED_VM_EXEC_CONTROL:
|
||||
current_evmcs->cpu_based_vm_exec_control = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_PROC;
|
||||
break;
|
||||
case EXCEPTION_BITMAP:
|
||||
current_evmcs->exception_bitmap = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_EXCPN;
|
||||
break;
|
||||
case VM_ENTRY_CONTROLS:
|
||||
current_evmcs->vm_entry_controls = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_ENTRY;
|
||||
break;
|
||||
case VM_ENTRY_INTR_INFO_FIELD:
|
||||
current_evmcs->vm_entry_intr_info_field = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_EVENT;
|
||||
break;
|
||||
case VM_ENTRY_EXCEPTION_ERROR_CODE:
|
||||
current_evmcs->vm_entry_exception_error_code = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_EVENT;
|
||||
break;
|
||||
case VM_ENTRY_INSTRUCTION_LEN:
|
||||
current_evmcs->vm_entry_instruction_len = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_EVENT;
|
||||
break;
|
||||
case HOST_IA32_SYSENTER_CS:
|
||||
current_evmcs->host_ia32_sysenter_cs = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case PIN_BASED_VM_EXEC_CONTROL:
|
||||
current_evmcs->pin_based_vm_exec_control = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP1;
|
||||
break;
|
||||
case VM_EXIT_CONTROLS:
|
||||
current_evmcs->vm_exit_controls = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP1;
|
||||
break;
|
||||
case SECONDARY_VM_EXEC_CONTROL:
|
||||
current_evmcs->secondary_vm_exec_control = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_GRP1;
|
||||
break;
|
||||
case GUEST_ES_LIMIT:
|
||||
current_evmcs->guest_es_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_CS_LIMIT:
|
||||
current_evmcs->guest_cs_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_SS_LIMIT:
|
||||
current_evmcs->guest_ss_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_DS_LIMIT:
|
||||
current_evmcs->guest_ds_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_FS_LIMIT:
|
||||
current_evmcs->guest_fs_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_GS_LIMIT:
|
||||
current_evmcs->guest_gs_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_LDTR_LIMIT:
|
||||
current_evmcs->guest_ldtr_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_TR_LIMIT:
|
||||
current_evmcs->guest_tr_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_GDTR_LIMIT:
|
||||
current_evmcs->guest_gdtr_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_IDTR_LIMIT:
|
||||
current_evmcs->guest_idtr_limit = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_ES_AR_BYTES:
|
||||
current_evmcs->guest_es_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_CS_AR_BYTES:
|
||||
current_evmcs->guest_cs_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_SS_AR_BYTES:
|
||||
current_evmcs->guest_ss_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_DS_AR_BYTES:
|
||||
current_evmcs->guest_ds_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_FS_AR_BYTES:
|
||||
current_evmcs->guest_fs_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_GS_AR_BYTES:
|
||||
current_evmcs->guest_gs_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_LDTR_AR_BYTES:
|
||||
current_evmcs->guest_ldtr_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_TR_AR_BYTES:
|
||||
current_evmcs->guest_tr_ar_bytes = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_ACTIVITY_STATE:
|
||||
current_evmcs->guest_activity_state = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case GUEST_SYSENTER_CS:
|
||||
current_evmcs->guest_sysenter_cs = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP1;
|
||||
break;
|
||||
case VM_INSTRUCTION_ERROR:
|
||||
current_evmcs->vm_instruction_error = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case VM_EXIT_REASON:
|
||||
current_evmcs->vm_exit_reason = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case VM_EXIT_INTR_INFO:
|
||||
current_evmcs->vm_exit_intr_info = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case VM_EXIT_INTR_ERROR_CODE:
|
||||
current_evmcs->vm_exit_intr_error_code = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case IDT_VECTORING_INFO_FIELD:
|
||||
current_evmcs->idt_vectoring_info_field = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case IDT_VECTORING_ERROR_CODE:
|
||||
current_evmcs->idt_vectoring_error_code = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case VM_EXIT_INSTRUCTION_LEN:
|
||||
current_evmcs->vm_exit_instruction_len = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case VMX_INSTRUCTION_INFO:
|
||||
current_evmcs->vmx_instruction_info = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_NONE;
|
||||
break;
|
||||
case PAGE_FAULT_ERROR_CODE_MASK:
|
||||
current_evmcs->page_fault_error_code_mask = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case PAGE_FAULT_ERROR_CODE_MATCH:
|
||||
current_evmcs->page_fault_error_code_match = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case CR3_TARGET_COUNT:
|
||||
current_evmcs->cr3_target_count = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case VM_EXIT_MSR_STORE_COUNT:
|
||||
current_evmcs->vm_exit_msr_store_count = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case VM_EXIT_MSR_LOAD_COUNT:
|
||||
current_evmcs->vm_exit_msr_load_count = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case VM_ENTRY_MSR_LOAD_COUNT:
|
||||
current_evmcs->vm_entry_msr_load_count = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_ALL;
|
||||
break;
|
||||
case HOST_ES_SELECTOR:
|
||||
current_evmcs->host_es_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_CS_SELECTOR:
|
||||
current_evmcs->host_cs_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_SS_SELECTOR:
|
||||
current_evmcs->host_ss_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_DS_SELECTOR:
|
||||
current_evmcs->host_ds_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_FS_SELECTOR:
|
||||
current_evmcs->host_fs_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_GS_SELECTOR:
|
||||
current_evmcs->host_gs_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case HOST_TR_SELECTOR:
|
||||
current_evmcs->host_tr_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
break;
|
||||
case GUEST_ES_SELECTOR:
|
||||
current_evmcs->guest_es_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_CS_SELECTOR:
|
||||
current_evmcs->guest_cs_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_SS_SELECTOR:
|
||||
current_evmcs->guest_ss_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_DS_SELECTOR:
|
||||
current_evmcs->guest_ds_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_FS_SELECTOR:
|
||||
current_evmcs->guest_fs_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_GS_SELECTOR:
|
||||
current_evmcs->guest_gs_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_LDTR_SELECTOR:
|
||||
current_evmcs->guest_ldtr_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case GUEST_TR_SELECTOR:
|
||||
current_evmcs->guest_tr_selector = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_GUEST_GRP2;
|
||||
break;
|
||||
case VIRTUAL_PROCESSOR_ID:
|
||||
current_evmcs->virtual_processor_id = value;
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_CONTROL_XLAT;
|
||||
break;
|
||||
default: return 1;
|
||||
}
|
||||
@@ -1070,7 +1215,10 @@ static inline int evmcs_vmresume(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
current_evmcs->hv_clean_fields = 0;
|
||||
/* HOST_RIP */
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_GRP1;
|
||||
/* HOST_RSP */
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_HOST_POINTER;
|
||||
|
||||
__asm__ __volatile__("push %%rbp;"
|
||||
"push %%rcx;"
|
||||
|
||||
@@ -363,6 +363,11 @@ static inline unsigned long get_xmm(int n)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void cpu_relax(void)
|
||||
{
|
||||
asm volatile("rep; nop" ::: "memory");
|
||||
}
|
||||
|
||||
bool is_intel_cpu(void);
|
||||
bool is_amd_cpu(void);
|
||||
|
||||
|
||||
@@ -99,7 +99,14 @@ struct __attribute__ ((__packed__)) vmcb_control_area {
|
||||
u8 reserved_6[8]; /* Offset 0xe8 */
|
||||
u64 avic_logical_id; /* Offset 0xf0 */
|
||||
u64 avic_physical_id; /* Offset 0xf8 */
|
||||
u8 reserved_7[768];
|
||||
u8 reserved_7[8];
|
||||
u64 vmsa_pa; /* Used for an SEV-ES guest */
|
||||
u8 reserved_8[720];
|
||||
/*
|
||||
* Offset 0x3e0, 32 bytes reserved
|
||||
* for use by hypervisor/software.
|
||||
*/
|
||||
u8 reserved_sw[32];
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#define CPUID_SVM_BIT 2
|
||||
#define CPUID_SVM BIT_ULL(CPUID_SVM_BIT)
|
||||
|
||||
#define SVM_EXIT_MSR 0x07c
|
||||
#define SVM_EXIT_VMMCALL 0x081
|
||||
|
||||
struct svm_test_data {
|
||||
@@ -28,6 +29,11 @@ struct svm_test_data {
|
||||
struct vmcb_save_area *save_area; /* gva */
|
||||
void *save_area_hva;
|
||||
uint64_t save_area_gpa;
|
||||
|
||||
/* MSR-Bitmap */
|
||||
void *msr; /* gva */
|
||||
void *msr_hva;
|
||||
uint64_t msr_gpa;
|
||||
};
|
||||
|
||||
struct svm_test_data *vcpu_alloc_svm(struct kvm_vm *vm, vm_vaddr_t *p_svm_gva);
|
||||
|
||||
@@ -19,7 +19,7 @@ struct gicv3_data {
|
||||
unsigned int nr_spis;
|
||||
};
|
||||
|
||||
#define sgi_base_from_redist(redist_base) (redist_base + SZ_64K)
|
||||
#define sgi_base_from_redist(redist_base) (redist_base + SZ_64K)
|
||||
#define DIST_BIT (1U << 31)
|
||||
|
||||
enum gicv3_intid_range {
|
||||
@@ -105,7 +105,8 @@ static void gicv3_set_eoi_split(bool split)
|
||||
{
|
||||
uint32_t val;
|
||||
|
||||
/* All other fields are read-only, so no need to read CTLR first. In
|
||||
/*
|
||||
* All other fields are read-only, so no need to read CTLR first. In
|
||||
* fact, the kernel does the same.
|
||||
*/
|
||||
val = split ? (1U << 1) : 0;
|
||||
@@ -159,9 +160,10 @@ static void gicv3_access_reg(uint32_t intid, uint64_t offset,
|
||||
uint32_t cpu_or_dist;
|
||||
|
||||
GUEST_ASSERT(bits_per_field <= reg_bits);
|
||||
GUEST_ASSERT(*val < (1U << bits_per_field));
|
||||
/* Some registers like IROUTER are 64 bit long. Those are currently not
|
||||
* supported by readl nor writel, so just asserting here until then.
|
||||
GUEST_ASSERT(!write || *val < (1U << bits_per_field));
|
||||
/*
|
||||
* This function does not support 64 bit accesses. Just asserting here
|
||||
* until we implement readq/writeq.
|
||||
*/
|
||||
GUEST_ASSERT(reg_bits == 32);
|
||||
|
||||
|
||||
@@ -140,9 +140,6 @@ static void vgic_poke_irq(int gic_fd, uint32_t intid,
|
||||
uint64_t val;
|
||||
bool intid_is_private = INTID_IS_SGI(intid) || INTID_IS_PPI(intid);
|
||||
|
||||
/* Check that the addr part of the attr is within 32 bits. */
|
||||
assert(attr <= KVM_DEV_ARM_VGIC_OFFSET_MASK);
|
||||
|
||||
uint32_t group = intid_is_private ? KVM_DEV_ARM_VGIC_GRP_REDIST_REGS
|
||||
: KVM_DEV_ARM_VGIC_GRP_DIST_REGS;
|
||||
|
||||
@@ -152,7 +149,11 @@ static void vgic_poke_irq(int gic_fd, uint32_t intid,
|
||||
attr += SZ_64K;
|
||||
}
|
||||
|
||||
/* All calls will succeed, even with invalid intid's, as long as the
|
||||
/* Check that the addr part of the attr is within 32 bits. */
|
||||
assert((attr & ~KVM_DEV_ARM_VGIC_OFFSET_MASK) == 0);
|
||||
|
||||
/*
|
||||
* All calls will succeed, even with invalid intid's, as long as the
|
||||
* addr part of the attr is within 32 bits (checked above). An invalid
|
||||
* intid will just make the read/writes point to above the intended
|
||||
* register space (i.e., ICPENDR after ISPENDR).
|
||||
|
||||
@@ -362,6 +362,20 @@ struct kvm_vm *vm_create(enum vm_guest_mode mode, uint64_t phy_pages, int perm)
|
||||
return vm;
|
||||
}
|
||||
|
||||
struct kvm_vm *vm_create_without_vcpus(enum vm_guest_mode mode, uint64_t pages)
|
||||
{
|
||||
struct kvm_vm *vm;
|
||||
|
||||
vm = vm_create(mode, pages, O_RDWR);
|
||||
|
||||
kvm_vm_elf_load(vm, program_invocation_name);
|
||||
|
||||
#ifdef __x86_64__
|
||||
vm_create_irqchip(vm);
|
||||
#endif
|
||||
return vm;
|
||||
}
|
||||
|
||||
/*
|
||||
* VM Create with customized parameters
|
||||
*
|
||||
@@ -412,13 +426,8 @@ struct kvm_vm *vm_create_with_vcpus(enum vm_guest_mode mode, uint32_t nr_vcpus,
|
||||
nr_vcpus, kvm_check_cap(KVM_CAP_MAX_VCPUS));
|
||||
|
||||
pages = vm_adjust_num_guest_pages(mode, pages);
|
||||
vm = vm_create(mode, pages, O_RDWR);
|
||||
|
||||
kvm_vm_elf_load(vm, program_invocation_name);
|
||||
|
||||
#ifdef __x86_64__
|
||||
vm_create_irqchip(vm);
|
||||
#endif
|
||||
vm = vm_create_without_vcpus(mode, pages);
|
||||
|
||||
for (i = 0; i < nr_vcpus; ++i) {
|
||||
uint32_t vcpuid = vcpuids ? vcpuids[i] : i;
|
||||
@@ -709,6 +718,27 @@ void kvm_vm_free(struct kvm_vm *vmp)
|
||||
free(vmp);
|
||||
}
|
||||
|
||||
int kvm_memfd_alloc(size_t size, bool hugepages)
|
||||
{
|
||||
int memfd_flags = MFD_CLOEXEC;
|
||||
int fd, r;
|
||||
|
||||
if (hugepages)
|
||||
memfd_flags |= MFD_HUGETLB;
|
||||
|
||||
fd = memfd_create("kvm_selftest", memfd_flags);
|
||||
TEST_ASSERT(fd != -1, "memfd_create() failed, errno: %i (%s)",
|
||||
errno, strerror(errno));
|
||||
|
||||
r = ftruncate(fd, size);
|
||||
TEST_ASSERT(!r, "ftruncate() failed, errno: %i (%s)", errno, strerror(errno));
|
||||
|
||||
r = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0, size);
|
||||
TEST_ASSERT(!r, "fallocate() failed, errno: %i (%s)", errno, strerror(errno));
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Memory Compare, host virtual to guest virtual
|
||||
*
|
||||
@@ -830,6 +860,30 @@ static void vm_userspace_mem_region_hva_insert(struct rb_root *hva_tree,
|
||||
rb_insert_color(®ion->hva_node, hva_tree);
|
||||
}
|
||||
|
||||
|
||||
int __vm_set_user_memory_region(struct kvm_vm *vm, uint32_t slot, uint32_t flags,
|
||||
uint64_t gpa, uint64_t size, void *hva)
|
||||
{
|
||||
struct kvm_userspace_memory_region region = {
|
||||
.slot = slot,
|
||||
.flags = flags,
|
||||
.guest_phys_addr = gpa,
|
||||
.memory_size = size,
|
||||
.userspace_addr = (uintptr_t)hva,
|
||||
};
|
||||
|
||||
return ioctl(vm->fd, KVM_SET_USER_MEMORY_REGION, ®ion);
|
||||
}
|
||||
|
||||
void vm_set_user_memory_region(struct kvm_vm *vm, uint32_t slot, uint32_t flags,
|
||||
uint64_t gpa, uint64_t size, void *hva)
|
||||
{
|
||||
int ret = __vm_set_user_memory_region(vm, slot, flags, gpa, size, hva);
|
||||
|
||||
TEST_ASSERT(!ret, "KVM_SET_USER_MEMORY_REGION failed, errno = %d (%s)",
|
||||
errno, strerror(errno));
|
||||
}
|
||||
|
||||
/*
|
||||
* VM Userspace Memory Region Add
|
||||
*
|
||||
@@ -937,24 +991,9 @@ void vm_userspace_mem_region_add(struct kvm_vm *vm,
|
||||
region->mmap_size += alignment;
|
||||
|
||||
region->fd = -1;
|
||||
if (backing_src_is_shared(src_type)) {
|
||||
int memfd_flags = MFD_CLOEXEC;
|
||||
|
||||
if (src_type == VM_MEM_SRC_SHARED_HUGETLB)
|
||||
memfd_flags |= MFD_HUGETLB;
|
||||
|
||||
region->fd = memfd_create("kvm_selftest", memfd_flags);
|
||||
TEST_ASSERT(region->fd != -1,
|
||||
"memfd_create failed, errno: %i", errno);
|
||||
|
||||
ret = ftruncate(region->fd, region->mmap_size);
|
||||
TEST_ASSERT(ret == 0, "ftruncate failed, errno: %i", errno);
|
||||
|
||||
ret = fallocate(region->fd,
|
||||
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, 0,
|
||||
region->mmap_size);
|
||||
TEST_ASSERT(ret == 0, "fallocate failed, errno: %i", errno);
|
||||
}
|
||||
if (backing_src_is_shared(src_type))
|
||||
region->fd = kvm_memfd_alloc(region->mmap_size,
|
||||
src_type == VM_MEM_SRC_SHARED_HUGETLB);
|
||||
|
||||
region->mmap_start = mmap(NULL, region->mmap_size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
|
||||
@@ -43,6 +43,11 @@ vcpu_alloc_svm(struct kvm_vm *vm, vm_vaddr_t *p_svm_gva)
|
||||
svm->save_area_hva = addr_gva2hva(vm, (uintptr_t)svm->save_area);
|
||||
svm->save_area_gpa = addr_gva2gpa(vm, (uintptr_t)svm->save_area);
|
||||
|
||||
svm->msr = (void *)vm_vaddr_alloc_page(vm);
|
||||
svm->msr_hva = addr_gva2hva(vm, (uintptr_t)svm->msr);
|
||||
svm->msr_gpa = addr_gva2gpa(vm, (uintptr_t)svm->msr);
|
||||
memset(svm->msr_hva, 0, getpagesize());
|
||||
|
||||
*p_svm_gva = svm_gva;
|
||||
return svm;
|
||||
}
|
||||
@@ -106,6 +111,7 @@ void generic_svm_setup(struct svm_test_data *svm, void *guest_rip, void *guest_r
|
||||
save->dbgctl = rdmsr(MSR_IA32_DEBUGCTLMSR);
|
||||
ctrl->intercept = (1ULL << INTERCEPT_VMRUN) |
|
||||
(1ULL << INTERCEPT_VMMCALL);
|
||||
ctrl->msrpm_base_pa = svm->msr_gpa;
|
||||
|
||||
vmcb->save.rip = (u64)guest_rip;
|
||||
vmcb->save.rsp = (u64)guest_rsp;
|
||||
|
||||
292
tools/testing/selftests/kvm/max_guest_memory_test.c
Normal file
292
tools/testing/selftests/kvm/max_guest_memory_test.c
Normal file
@@ -0,0 +1,292 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <sys/types.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <linux/bitmap.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include "kvm_util.h"
|
||||
#include "test_util.h"
|
||||
#include "guest_modes.h"
|
||||
#include "processor.h"
|
||||
|
||||
static void guest_code(uint64_t start_gpa, uint64_t end_gpa, uint64_t stride)
|
||||
{
|
||||
uint64_t gpa;
|
||||
|
||||
for (gpa = start_gpa; gpa < end_gpa; gpa += stride)
|
||||
*((volatile uint64_t *)gpa) = gpa;
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
struct vcpu_info {
|
||||
struct kvm_vm *vm;
|
||||
uint32_t id;
|
||||
uint64_t start_gpa;
|
||||
uint64_t end_gpa;
|
||||
};
|
||||
|
||||
static int nr_vcpus;
|
||||
static atomic_t rendezvous;
|
||||
|
||||
static void rendezvous_with_boss(void)
|
||||
{
|
||||
int orig = atomic_read(&rendezvous);
|
||||
|
||||
if (orig > 0) {
|
||||
atomic_dec_and_test(&rendezvous);
|
||||
while (atomic_read(&rendezvous) > 0)
|
||||
cpu_relax();
|
||||
} else {
|
||||
atomic_inc(&rendezvous);
|
||||
while (atomic_read(&rendezvous) < 0)
|
||||
cpu_relax();
|
||||
}
|
||||
}
|
||||
|
||||
static void run_vcpu(struct kvm_vm *vm, uint32_t vcpu_id)
|
||||
{
|
||||
vcpu_run(vm, vcpu_id);
|
||||
ASSERT_EQ(get_ucall(vm, vcpu_id, NULL), UCALL_DONE);
|
||||
}
|
||||
|
||||
static void *vcpu_worker(void *data)
|
||||
{
|
||||
struct vcpu_info *vcpu = data;
|
||||
struct kvm_vm *vm = vcpu->vm;
|
||||
struct kvm_sregs sregs;
|
||||
struct kvm_regs regs;
|
||||
|
||||
vcpu_args_set(vm, vcpu->id, 3, vcpu->start_gpa, vcpu->end_gpa,
|
||||
vm_get_page_size(vm));
|
||||
|
||||
/* Snapshot regs before the first run. */
|
||||
vcpu_regs_get(vm, vcpu->id, ®s);
|
||||
rendezvous_with_boss();
|
||||
|
||||
run_vcpu(vm, vcpu->id);
|
||||
rendezvous_with_boss();
|
||||
vcpu_regs_set(vm, vcpu->id, ®s);
|
||||
vcpu_sregs_get(vm, vcpu->id, &sregs);
|
||||
#ifdef __x86_64__
|
||||
/* Toggle CR0.WP to trigger a MMU context reset. */
|
||||
sregs.cr0 ^= X86_CR0_WP;
|
||||
#endif
|
||||
vcpu_sregs_set(vm, vcpu->id, &sregs);
|
||||
rendezvous_with_boss();
|
||||
|
||||
run_vcpu(vm, vcpu->id);
|
||||
rendezvous_with_boss();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pthread_t *spawn_workers(struct kvm_vm *vm, uint64_t start_gpa,
|
||||
uint64_t end_gpa)
|
||||
{
|
||||
struct vcpu_info *info;
|
||||
uint64_t gpa, nr_bytes;
|
||||
pthread_t *threads;
|
||||
int i;
|
||||
|
||||
threads = malloc(nr_vcpus * sizeof(*threads));
|
||||
TEST_ASSERT(threads, "Failed to allocate vCPU threads");
|
||||
|
||||
info = malloc(nr_vcpus * sizeof(*info));
|
||||
TEST_ASSERT(info, "Failed to allocate vCPU gpa ranges");
|
||||
|
||||
nr_bytes = ((end_gpa - start_gpa) / nr_vcpus) &
|
||||
~((uint64_t)vm_get_page_size(vm) - 1);
|
||||
TEST_ASSERT(nr_bytes, "C'mon, no way you have %d CPUs", nr_vcpus);
|
||||
|
||||
for (i = 0, gpa = start_gpa; i < nr_vcpus; i++, gpa += nr_bytes) {
|
||||
info[i].vm = vm;
|
||||
info[i].id = i;
|
||||
info[i].start_gpa = gpa;
|
||||
info[i].end_gpa = gpa + nr_bytes;
|
||||
pthread_create(&threads[i], NULL, vcpu_worker, &info[i]);
|
||||
}
|
||||
return threads;
|
||||
}
|
||||
|
||||
static void rendezvous_with_vcpus(struct timespec *time, const char *name)
|
||||
{
|
||||
int i, rendezvoused;
|
||||
|
||||
pr_info("Waiting for vCPUs to finish %s...\n", name);
|
||||
|
||||
rendezvoused = atomic_read(&rendezvous);
|
||||
for (i = 0; abs(rendezvoused) != 1; i++) {
|
||||
usleep(100);
|
||||
if (!(i & 0x3f))
|
||||
pr_info("\r%d vCPUs haven't rendezvoused...",
|
||||
abs(rendezvoused) - 1);
|
||||
rendezvoused = atomic_read(&rendezvous);
|
||||
}
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, time);
|
||||
|
||||
/* Release the vCPUs after getting the time of the previous action. */
|
||||
pr_info("\rAll vCPUs finished %s, releasing...\n", name);
|
||||
if (rendezvoused > 0)
|
||||
atomic_set(&rendezvous, -nr_vcpus - 1);
|
||||
else
|
||||
atomic_set(&rendezvous, nr_vcpus + 1);
|
||||
}
|
||||
|
||||
static void calc_default_nr_vcpus(void)
|
||||
{
|
||||
cpu_set_t possible_mask;
|
||||
int r;
|
||||
|
||||
r = sched_getaffinity(0, sizeof(possible_mask), &possible_mask);
|
||||
TEST_ASSERT(!r, "sched_getaffinity failed, errno = %d (%s)",
|
||||
errno, strerror(errno));
|
||||
|
||||
nr_vcpus = CPU_COUNT(&possible_mask) * 3/4;
|
||||
TEST_ASSERT(nr_vcpus > 0, "Uh, no CPUs?");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
/*
|
||||
* Skip the first 4gb and slot0. slot0 maps <1gb and is used to back
|
||||
* the guest's code, stack, and page tables. Because selftests creates
|
||||
* an IRQCHIP, a.k.a. a local APIC, KVM creates an internal memslot
|
||||
* just below the 4gb boundary. This test could create memory at
|
||||
* 1gb-3gb,but it's simpler to skip straight to 4gb.
|
||||
*/
|
||||
const uint64_t size_1gb = (1 << 30);
|
||||
const uint64_t start_gpa = (4ull * size_1gb);
|
||||
const int first_slot = 1;
|
||||
|
||||
struct timespec time_start, time_run1, time_reset, time_run2;
|
||||
uint64_t max_gpa, gpa, slot_size, max_mem, i;
|
||||
int max_slots, slot, opt, fd;
|
||||
bool hugepages = false;
|
||||
pthread_t *threads;
|
||||
struct kvm_vm *vm;
|
||||
void *mem;
|
||||
|
||||
/*
|
||||
* Default to 2gb so that maxing out systems with MAXPHADDR=46, which
|
||||
* are quite common for x86, requires changing only max_mem (KVM allows
|
||||
* 32k memslots, 32k * 2gb == ~64tb of guest memory).
|
||||
*/
|
||||
slot_size = 2 * size_1gb;
|
||||
|
||||
max_slots = kvm_check_cap(KVM_CAP_NR_MEMSLOTS);
|
||||
TEST_ASSERT(max_slots > first_slot, "KVM is broken");
|
||||
|
||||
/* All KVM MMUs should be able to survive a 128gb guest. */
|
||||
max_mem = 128 * size_1gb;
|
||||
|
||||
calc_default_nr_vcpus();
|
||||
|
||||
while ((opt = getopt(argc, argv, "c:h:m:s:H")) != -1) {
|
||||
switch (opt) {
|
||||
case 'c':
|
||||
nr_vcpus = atoi(optarg);
|
||||
TEST_ASSERT(nr_vcpus > 0, "number of vcpus must be >0");
|
||||
break;
|
||||
case 'm':
|
||||
max_mem = atoi(optarg) * size_1gb;
|
||||
TEST_ASSERT(max_mem > 0, "memory size must be >0");
|
||||
break;
|
||||
case 's':
|
||||
slot_size = atoi(optarg) * size_1gb;
|
||||
TEST_ASSERT(slot_size > 0, "slot size must be >0");
|
||||
break;
|
||||
case 'H':
|
||||
hugepages = true;
|
||||
break;
|
||||
case 'h':
|
||||
default:
|
||||
printf("usage: %s [-c nr_vcpus] [-m max_mem_in_gb] [-s slot_size_in_gb] [-H]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
vm = vm_create_default_with_vcpus(nr_vcpus, 0, 0, guest_code, NULL);
|
||||
|
||||
max_gpa = vm_get_max_gfn(vm) << vm_get_page_shift(vm);
|
||||
TEST_ASSERT(max_gpa > (4 * slot_size), "MAXPHYADDR <4gb ");
|
||||
|
||||
fd = kvm_memfd_alloc(slot_size, hugepages);
|
||||
mem = mmap(NULL, slot_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
TEST_ASSERT(mem != MAP_FAILED, "mmap() failed");
|
||||
|
||||
TEST_ASSERT(!madvise(mem, slot_size, MADV_NOHUGEPAGE), "madvise() failed");
|
||||
|
||||
/* Pre-fault the memory to avoid taking mmap_sem on guest page faults. */
|
||||
for (i = 0; i < slot_size; i += vm_get_page_size(vm))
|
||||
((uint8_t *)mem)[i] = 0xaa;
|
||||
|
||||
gpa = 0;
|
||||
for (slot = first_slot; slot < max_slots; slot++) {
|
||||
gpa = start_gpa + ((slot - first_slot) * slot_size);
|
||||
if (gpa + slot_size > max_gpa)
|
||||
break;
|
||||
|
||||
if ((gpa - start_gpa) >= max_mem)
|
||||
break;
|
||||
|
||||
vm_set_user_memory_region(vm, slot, 0, gpa, slot_size, mem);
|
||||
|
||||
#ifdef __x86_64__
|
||||
/* Identity map memory in the guest using 1gb pages. */
|
||||
for (i = 0; i < slot_size; i += size_1gb)
|
||||
__virt_pg_map(vm, gpa + i, gpa + i, X86_PAGE_SIZE_1G);
|
||||
#else
|
||||
for (i = 0; i < slot_size; i += vm_get_page_size(vm))
|
||||
virt_pg_map(vm, gpa + i, gpa + i);
|
||||
#endif
|
||||
}
|
||||
|
||||
atomic_set(&rendezvous, nr_vcpus + 1);
|
||||
threads = spawn_workers(vm, start_gpa, gpa);
|
||||
|
||||
pr_info("Running with %lugb of guest memory and %u vCPUs\n",
|
||||
(gpa - start_gpa) / size_1gb, nr_vcpus);
|
||||
|
||||
rendezvous_with_vcpus(&time_start, "spawning");
|
||||
rendezvous_with_vcpus(&time_run1, "run 1");
|
||||
rendezvous_with_vcpus(&time_reset, "reset");
|
||||
rendezvous_with_vcpus(&time_run2, "run 2");
|
||||
|
||||
time_run2 = timespec_sub(time_run2, time_reset);
|
||||
time_reset = timespec_sub(time_reset, time_run1);
|
||||
time_run1 = timespec_sub(time_run1, time_start);
|
||||
|
||||
pr_info("run1 = %ld.%.9lds, reset = %ld.%.9lds, run2 = %ld.%.9lds\n",
|
||||
time_run1.tv_sec, time_run1.tv_nsec,
|
||||
time_reset.tv_sec, time_reset.tv_nsec,
|
||||
time_run2.tv_sec, time_run2.tv_nsec);
|
||||
|
||||
/*
|
||||
* Delete even numbered slots (arbitrary) and unmap the first half of
|
||||
* the backing (also arbitrary) to verify KVM correctly drops all
|
||||
* references to the removed regions.
|
||||
*/
|
||||
for (slot = (slot - 1) & ~1ull; slot >= first_slot; slot -= 2)
|
||||
vm_set_user_memory_region(vm, slot, 0, 0, 0, NULL);
|
||||
|
||||
munmap(mem, slot_size / 2);
|
||||
|
||||
/* Sanity check that the vCPUs actually ran. */
|
||||
for (i = 0; i < nr_vcpus; i++)
|
||||
pthread_join(threads[i], NULL);
|
||||
|
||||
/*
|
||||
* Deliberately exit without deleting the remaining memslots or closing
|
||||
* kvm_fd to test cleanup via mmu_notifier.release.
|
||||
*/
|
||||
}
|
||||
@@ -13,154 +13,668 @@
|
||||
#include "test_util.h"
|
||||
#include "kvm_util.h"
|
||||
|
||||
enum mop_target {
|
||||
LOGICAL,
|
||||
SIDA,
|
||||
ABSOLUTE,
|
||||
INVALID,
|
||||
};
|
||||
|
||||
enum mop_access_mode {
|
||||
READ,
|
||||
WRITE,
|
||||
};
|
||||
|
||||
struct mop_desc {
|
||||
uintptr_t gaddr;
|
||||
uintptr_t gaddr_v;
|
||||
uint64_t set_flags;
|
||||
unsigned int f_check : 1;
|
||||
unsigned int f_inject : 1;
|
||||
unsigned int f_key : 1;
|
||||
unsigned int _gaddr_v : 1;
|
||||
unsigned int _set_flags : 1;
|
||||
unsigned int _sida_offset : 1;
|
||||
unsigned int _ar : 1;
|
||||
uint32_t size;
|
||||
enum mop_target target;
|
||||
enum mop_access_mode mode;
|
||||
void *buf;
|
||||
uint32_t sida_offset;
|
||||
uint8_t ar;
|
||||
uint8_t key;
|
||||
};
|
||||
|
||||
static struct kvm_s390_mem_op ksmo_from_desc(struct mop_desc desc)
|
||||
{
|
||||
struct kvm_s390_mem_op ksmo = {
|
||||
.gaddr = (uintptr_t)desc.gaddr,
|
||||
.size = desc.size,
|
||||
.buf = ((uintptr_t)desc.buf),
|
||||
.reserved = "ignored_ignored_ignored_ignored"
|
||||
};
|
||||
|
||||
switch (desc.target) {
|
||||
case LOGICAL:
|
||||
if (desc.mode == READ)
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_READ;
|
||||
if (desc.mode == WRITE)
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
break;
|
||||
case SIDA:
|
||||
if (desc.mode == READ)
|
||||
ksmo.op = KVM_S390_MEMOP_SIDA_READ;
|
||||
if (desc.mode == WRITE)
|
||||
ksmo.op = KVM_S390_MEMOP_SIDA_WRITE;
|
||||
break;
|
||||
case ABSOLUTE:
|
||||
if (desc.mode == READ)
|
||||
ksmo.op = KVM_S390_MEMOP_ABSOLUTE_READ;
|
||||
if (desc.mode == WRITE)
|
||||
ksmo.op = KVM_S390_MEMOP_ABSOLUTE_WRITE;
|
||||
break;
|
||||
case INVALID:
|
||||
ksmo.op = -1;
|
||||
}
|
||||
if (desc.f_check)
|
||||
ksmo.flags |= KVM_S390_MEMOP_F_CHECK_ONLY;
|
||||
if (desc.f_inject)
|
||||
ksmo.flags |= KVM_S390_MEMOP_F_INJECT_EXCEPTION;
|
||||
if (desc._set_flags)
|
||||
ksmo.flags = desc.set_flags;
|
||||
if (desc.f_key) {
|
||||
ksmo.flags |= KVM_S390_MEMOP_F_SKEY_PROTECTION;
|
||||
ksmo.key = desc.key;
|
||||
}
|
||||
if (desc._ar)
|
||||
ksmo.ar = desc.ar;
|
||||
else
|
||||
ksmo.ar = 0;
|
||||
if (desc._sida_offset)
|
||||
ksmo.sida_offset = desc.sida_offset;
|
||||
|
||||
return ksmo;
|
||||
}
|
||||
|
||||
/* vcpu dummy id signifying that vm instead of vcpu ioctl is to occur */
|
||||
const uint32_t VM_VCPU_ID = (uint32_t)-1;
|
||||
|
||||
struct test_vcpu {
|
||||
struct kvm_vm *vm;
|
||||
uint32_t id;
|
||||
};
|
||||
|
||||
#define PRINT_MEMOP false
|
||||
static void print_memop(uint32_t vcpu_id, const struct kvm_s390_mem_op *ksmo)
|
||||
{
|
||||
if (!PRINT_MEMOP)
|
||||
return;
|
||||
|
||||
if (vcpu_id == VM_VCPU_ID)
|
||||
printf("vm memop(");
|
||||
else
|
||||
printf("vcpu memop(");
|
||||
switch (ksmo->op) {
|
||||
case KVM_S390_MEMOP_LOGICAL_READ:
|
||||
printf("LOGICAL, READ, ");
|
||||
break;
|
||||
case KVM_S390_MEMOP_LOGICAL_WRITE:
|
||||
printf("LOGICAL, WRITE, ");
|
||||
break;
|
||||
case KVM_S390_MEMOP_SIDA_READ:
|
||||
printf("SIDA, READ, ");
|
||||
break;
|
||||
case KVM_S390_MEMOP_SIDA_WRITE:
|
||||
printf("SIDA, WRITE, ");
|
||||
break;
|
||||
case KVM_S390_MEMOP_ABSOLUTE_READ:
|
||||
printf("ABSOLUTE, READ, ");
|
||||
break;
|
||||
case KVM_S390_MEMOP_ABSOLUTE_WRITE:
|
||||
printf("ABSOLUTE, WRITE, ");
|
||||
break;
|
||||
}
|
||||
printf("gaddr=%llu, size=%u, buf=%llu, ar=%u, key=%u",
|
||||
ksmo->gaddr, ksmo->size, ksmo->buf, ksmo->ar, ksmo->key);
|
||||
if (ksmo->flags & KVM_S390_MEMOP_F_CHECK_ONLY)
|
||||
printf(", CHECK_ONLY");
|
||||
if (ksmo->flags & KVM_S390_MEMOP_F_INJECT_EXCEPTION)
|
||||
printf(", INJECT_EXCEPTION");
|
||||
if (ksmo->flags & KVM_S390_MEMOP_F_SKEY_PROTECTION)
|
||||
printf(", SKEY_PROTECTION");
|
||||
puts(")");
|
||||
}
|
||||
|
||||
static void memop_ioctl(struct test_vcpu vcpu, struct kvm_s390_mem_op *ksmo)
|
||||
{
|
||||
if (vcpu.id == VM_VCPU_ID)
|
||||
vm_ioctl(vcpu.vm, KVM_S390_MEM_OP, ksmo);
|
||||
else
|
||||
vcpu_ioctl(vcpu.vm, vcpu.id, KVM_S390_MEM_OP, ksmo);
|
||||
}
|
||||
|
||||
static int err_memop_ioctl(struct test_vcpu vcpu, struct kvm_s390_mem_op *ksmo)
|
||||
{
|
||||
if (vcpu.id == VM_VCPU_ID)
|
||||
return _vm_ioctl(vcpu.vm, KVM_S390_MEM_OP, ksmo);
|
||||
else
|
||||
return _vcpu_ioctl(vcpu.vm, vcpu.id, KVM_S390_MEM_OP, ksmo);
|
||||
}
|
||||
|
||||
#define MEMOP(err, vcpu_p, mop_target_p, access_mode_p, buf_p, size_p, ...) \
|
||||
({ \
|
||||
struct test_vcpu __vcpu = (vcpu_p); \
|
||||
struct mop_desc __desc = { \
|
||||
.target = (mop_target_p), \
|
||||
.mode = (access_mode_p), \
|
||||
.buf = (buf_p), \
|
||||
.size = (size_p), \
|
||||
__VA_ARGS__ \
|
||||
}; \
|
||||
struct kvm_s390_mem_op __ksmo; \
|
||||
\
|
||||
if (__desc._gaddr_v) { \
|
||||
if (__desc.target == ABSOLUTE) \
|
||||
__desc.gaddr = addr_gva2gpa(__vcpu.vm, __desc.gaddr_v); \
|
||||
else \
|
||||
__desc.gaddr = __desc.gaddr_v; \
|
||||
} \
|
||||
__ksmo = ksmo_from_desc(__desc); \
|
||||
print_memop(__vcpu.id, &__ksmo); \
|
||||
err##memop_ioctl(__vcpu, &__ksmo); \
|
||||
})
|
||||
|
||||
#define MOP(...) MEMOP(, __VA_ARGS__)
|
||||
#define ERR_MOP(...) MEMOP(err_, __VA_ARGS__)
|
||||
|
||||
#define GADDR(a) .gaddr = ((uintptr_t)a)
|
||||
#define GADDR_V(v) ._gaddr_v = 1, .gaddr_v = ((uintptr_t)v)
|
||||
#define CHECK_ONLY .f_check = 1
|
||||
#define SET_FLAGS(f) ._set_flags = 1, .set_flags = (f)
|
||||
#define SIDA_OFFSET(o) ._sida_offset = 1, .sida_offset = (o)
|
||||
#define AR(a) ._ar = 1, .ar = (a)
|
||||
#define KEY(a) .f_key = 1, .key = (a)
|
||||
|
||||
#define CHECK_N_DO(f, ...) ({ f(__VA_ARGS__, CHECK_ONLY); f(__VA_ARGS__); })
|
||||
|
||||
#define VCPU_ID 1
|
||||
#define PAGE_SHIFT 12
|
||||
#define PAGE_SIZE (1ULL << PAGE_SHIFT)
|
||||
#define PAGE_MASK (~(PAGE_SIZE - 1))
|
||||
#define CR0_FETCH_PROTECTION_OVERRIDE (1UL << (63 - 38))
|
||||
#define CR0_STORAGE_PROTECTION_OVERRIDE (1UL << (63 - 39))
|
||||
|
||||
static uint8_t mem1[65536];
|
||||
static uint8_t mem2[65536];
|
||||
|
||||
static void guest_code(void)
|
||||
struct test_default {
|
||||
struct kvm_vm *kvm_vm;
|
||||
struct test_vcpu vm;
|
||||
struct test_vcpu vcpu;
|
||||
struct kvm_run *run;
|
||||
int size;
|
||||
};
|
||||
|
||||
static struct test_default test_default_init(void *guest_code)
|
||||
{
|
||||
struct test_default t;
|
||||
|
||||
t.size = min((size_t)kvm_check_cap(KVM_CAP_S390_MEM_OP), sizeof(mem1));
|
||||
t.kvm_vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
t.vm = (struct test_vcpu) { t.kvm_vm, VM_VCPU_ID };
|
||||
t.vcpu = (struct test_vcpu) { t.kvm_vm, VCPU_ID };
|
||||
t.run = vcpu_state(t.kvm_vm, VCPU_ID);
|
||||
return t;
|
||||
}
|
||||
|
||||
enum stage {
|
||||
/* Synced state set by host, e.g. DAT */
|
||||
STAGE_INITED,
|
||||
/* Guest did nothing */
|
||||
STAGE_IDLED,
|
||||
/* Guest set storage keys (specifics up to test case) */
|
||||
STAGE_SKEYS_SET,
|
||||
/* Guest copied memory (locations up to test case) */
|
||||
STAGE_COPIED,
|
||||
};
|
||||
|
||||
#define HOST_SYNC(vcpu_p, stage) \
|
||||
({ \
|
||||
struct test_vcpu __vcpu = (vcpu_p); \
|
||||
struct ucall uc; \
|
||||
int __stage = (stage); \
|
||||
\
|
||||
vcpu_run(__vcpu.vm, __vcpu.id); \
|
||||
get_ucall(__vcpu.vm, __vcpu.id, &uc); \
|
||||
ASSERT_EQ(uc.cmd, UCALL_SYNC); \
|
||||
ASSERT_EQ(uc.args[1], __stage); \
|
||||
}) \
|
||||
|
||||
static void prepare_mem12(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (;;) {
|
||||
for (i = 0; i < sizeof(mem2); i++)
|
||||
mem2[i] = mem1[i];
|
||||
GUEST_SYNC(0);
|
||||
for (i = 0; i < sizeof(mem1); i++)
|
||||
mem1[i] = rand();
|
||||
memset(mem2, 0xaa, sizeof(mem2));
|
||||
}
|
||||
|
||||
#define ASSERT_MEM_EQ(p1, p2, size) \
|
||||
TEST_ASSERT(!memcmp(p1, p2, size), "Memory contents do not match!")
|
||||
|
||||
#define DEFAULT_WRITE_READ(copy_cpu, mop_cpu, mop_target_p, size, ...) \
|
||||
({ \
|
||||
struct test_vcpu __copy_cpu = (copy_cpu), __mop_cpu = (mop_cpu); \
|
||||
enum mop_target __target = (mop_target_p); \
|
||||
uint32_t __size = (size); \
|
||||
\
|
||||
prepare_mem12(); \
|
||||
CHECK_N_DO(MOP, __mop_cpu, __target, WRITE, mem1, __size, \
|
||||
GADDR_V(mem1), ##__VA_ARGS__); \
|
||||
HOST_SYNC(__copy_cpu, STAGE_COPIED); \
|
||||
CHECK_N_DO(MOP, __mop_cpu, __target, READ, mem2, __size, \
|
||||
GADDR_V(mem2), ##__VA_ARGS__); \
|
||||
ASSERT_MEM_EQ(mem1, mem2, __size); \
|
||||
})
|
||||
|
||||
#define DEFAULT_READ(copy_cpu, mop_cpu, mop_target_p, size, ...) \
|
||||
({ \
|
||||
struct test_vcpu __copy_cpu = (copy_cpu), __mop_cpu = (mop_cpu); \
|
||||
enum mop_target __target = (mop_target_p); \
|
||||
uint32_t __size = (size); \
|
||||
\
|
||||
prepare_mem12(); \
|
||||
CHECK_N_DO(MOP, __mop_cpu, __target, WRITE, mem1, __size, \
|
||||
GADDR_V(mem1)); \
|
||||
HOST_SYNC(__copy_cpu, STAGE_COPIED); \
|
||||
CHECK_N_DO(MOP, __mop_cpu, __target, READ, mem2, __size, ##__VA_ARGS__);\
|
||||
ASSERT_MEM_EQ(mem1, mem2, __size); \
|
||||
})
|
||||
|
||||
static void guest_copy(void)
|
||||
{
|
||||
GUEST_SYNC(STAGE_INITED);
|
||||
memcpy(&mem2, &mem1, sizeof(mem2));
|
||||
GUEST_SYNC(STAGE_COPIED);
|
||||
}
|
||||
|
||||
static void test_copy(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy);
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vcpu, LOGICAL, t.size);
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void set_storage_key_range(void *addr, size_t len, uint8_t key)
|
||||
{
|
||||
uintptr_t _addr, abs, i;
|
||||
int not_mapped = 0;
|
||||
|
||||
_addr = (uintptr_t)addr;
|
||||
for (i = _addr & PAGE_MASK; i < _addr + len; i += PAGE_SIZE) {
|
||||
abs = i;
|
||||
asm volatile (
|
||||
"lra %[abs], 0(0,%[abs])\n"
|
||||
" jz 0f\n"
|
||||
" llill %[not_mapped],1\n"
|
||||
" j 1f\n"
|
||||
"0: sske %[key], %[abs]\n"
|
||||
"1:"
|
||||
: [abs] "+&a" (abs), [not_mapped] "+r" (not_mapped)
|
||||
: [key] "r" (key)
|
||||
: "cc"
|
||||
);
|
||||
GUEST_ASSERT_EQ(not_mapped, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
static void guest_copy_key(void)
|
||||
{
|
||||
struct kvm_vm *vm;
|
||||
struct kvm_run *run;
|
||||
struct kvm_s390_mem_op ksmo;
|
||||
int rv, i, maxsize;
|
||||
set_storage_key_range(mem1, sizeof(mem1), 0x90);
|
||||
set_storage_key_range(mem2, sizeof(mem2), 0x90);
|
||||
GUEST_SYNC(STAGE_SKEYS_SET);
|
||||
|
||||
setbuf(stdout, NULL); /* Tell stdout not to buffer its content */
|
||||
|
||||
maxsize = kvm_check_cap(KVM_CAP_S390_MEM_OP);
|
||||
if (!maxsize) {
|
||||
print_skip("CAP_S390_MEM_OP not supported");
|
||||
exit(KSFT_SKIP);
|
||||
for (;;) {
|
||||
memcpy(&mem2, &mem1, sizeof(mem2));
|
||||
GUEST_SYNC(STAGE_COPIED);
|
||||
}
|
||||
if (maxsize > sizeof(mem1))
|
||||
maxsize = sizeof(mem1);
|
||||
}
|
||||
|
||||
/* Create VM */
|
||||
vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
static void test_copy_key(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key);
|
||||
|
||||
for (i = 0; i < sizeof(mem1); i++)
|
||||
mem1[i] = i * i + i;
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* Set the first array */
|
||||
ksmo.gaddr = addr_gva2gpa(vm, (uintptr_t)mem1);
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 0;
|
||||
vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
/* vm, no key */
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vm, ABSOLUTE, t.size);
|
||||
|
||||
/* Let the guest code copy the first array to the second */
|
||||
vcpu_run(vm, VCPU_ID);
|
||||
TEST_ASSERT(run->exit_reason == KVM_EXIT_S390_SIEIC,
|
||||
"Unexpected exit reason: %u (%s)\n",
|
||||
run->exit_reason,
|
||||
exit_reason_str(run->exit_reason));
|
||||
/* vm/vcpu, machting key or key 0 */
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vcpu, LOGICAL, t.size, KEY(0));
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vcpu, LOGICAL, t.size, KEY(9));
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vm, ABSOLUTE, t.size, KEY(0));
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vm, ABSOLUTE, t.size, KEY(9));
|
||||
/*
|
||||
* There used to be different code paths for key handling depending on
|
||||
* if the region crossed a page boundary.
|
||||
* There currently are not, but the more tests the merrier.
|
||||
*/
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vcpu, LOGICAL, 1, KEY(0));
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vcpu, LOGICAL, 1, KEY(9));
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vm, ABSOLUTE, 1, KEY(0));
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vm, ABSOLUTE, 1, KEY(9));
|
||||
|
||||
memset(mem2, 0xaa, sizeof(mem2));
|
||||
/* vm/vcpu, mismatching keys on read, but no fetch protection */
|
||||
DEFAULT_READ(t.vcpu, t.vcpu, LOGICAL, t.size, GADDR_V(mem2), KEY(2));
|
||||
DEFAULT_READ(t.vcpu, t.vm, ABSOLUTE, t.size, GADDR_V(mem1), KEY(2));
|
||||
|
||||
/* Get the second array */
|
||||
ksmo.gaddr = (uintptr_t)mem2;
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_READ;
|
||||
ksmo.buf = (uintptr_t)mem2;
|
||||
ksmo.ar = 0;
|
||||
vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
TEST_ASSERT(!memcmp(mem1, mem2, maxsize),
|
||||
"Memory contents do not match!");
|
||||
static void guest_copy_key_fetch_prot(void)
|
||||
{
|
||||
/*
|
||||
* For some reason combining the first sync with override enablement
|
||||
* results in an exception when calling HOST_SYNC.
|
||||
*/
|
||||
GUEST_SYNC(STAGE_INITED);
|
||||
/* Storage protection override applies to both store and fetch. */
|
||||
set_storage_key_range(mem1, sizeof(mem1), 0x98);
|
||||
set_storage_key_range(mem2, sizeof(mem2), 0x98);
|
||||
GUEST_SYNC(STAGE_SKEYS_SET);
|
||||
|
||||
/* Check error conditions - first bad size: */
|
||||
ksmo.gaddr = (uintptr_t)mem1;
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = -1;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 0;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
for (;;) {
|
||||
memcpy(&mem2, &mem1, sizeof(mem2));
|
||||
GUEST_SYNC(STAGE_COPIED);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_copy_key_storage_prot_override(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot);
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
t.run->s.regs.crs[0] |= CR0_STORAGE_PROTECTION_OVERRIDE;
|
||||
t.run->kvm_dirty_regs = KVM_SYNC_CRS;
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vcpu, mismatching keys, storage protection override in effect */
|
||||
DEFAULT_WRITE_READ(t.vcpu, t.vcpu, LOGICAL, t.size, KEY(2));
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void test_copy_key_fetch_prot(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot);
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vm/vcpu, matching key, fetch protection in effect */
|
||||
DEFAULT_READ(t.vcpu, t.vcpu, LOGICAL, t.size, GADDR_V(mem2), KEY(9));
|
||||
DEFAULT_READ(t.vcpu, t.vm, ABSOLUTE, t.size, GADDR_V(mem2), KEY(9));
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
#define ERR_PROT_MOP(...) \
|
||||
({ \
|
||||
int rv; \
|
||||
\
|
||||
rv = ERR_MOP(__VA_ARGS__); \
|
||||
TEST_ASSERT(rv == 4, "Should result in protection exception"); \
|
||||
})
|
||||
|
||||
static void test_errors_key(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot);
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vm/vcpu, mismatching keys, fetch protection in effect */
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vcpu, LOGICAL, WRITE, mem1, t.size, GADDR_V(mem1), KEY(2));
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vcpu, LOGICAL, READ, mem2, t.size, GADDR_V(mem2), KEY(2));
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, WRITE, mem1, t.size, GADDR_V(mem1), KEY(2));
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, READ, mem2, t.size, GADDR_V(mem2), KEY(2));
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void test_errors_key_storage_prot_override(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot);
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
t.run->s.regs.crs[0] |= CR0_STORAGE_PROTECTION_OVERRIDE;
|
||||
t.run->kvm_dirty_regs = KVM_SYNC_CRS;
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vm, mismatching keys, storage protection override not applicable to vm */
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, WRITE, mem1, t.size, GADDR_V(mem1), KEY(2));
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, READ, mem2, t.size, GADDR_V(mem2), KEY(2));
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
const uint64_t last_page_addr = -PAGE_SIZE;
|
||||
|
||||
static void guest_copy_key_fetch_prot_override(void)
|
||||
{
|
||||
int i;
|
||||
char *page_0 = 0;
|
||||
|
||||
GUEST_SYNC(STAGE_INITED);
|
||||
set_storage_key_range(0, PAGE_SIZE, 0x18);
|
||||
set_storage_key_range((void *)last_page_addr, PAGE_SIZE, 0x0);
|
||||
asm volatile ("sske %[key],%[addr]\n" :: [addr] "r"(0), [key] "r"(0x18) : "cc");
|
||||
GUEST_SYNC(STAGE_SKEYS_SET);
|
||||
|
||||
for (;;) {
|
||||
for (i = 0; i < PAGE_SIZE; i++)
|
||||
page_0[i] = mem1[i];
|
||||
GUEST_SYNC(STAGE_COPIED);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_copy_key_fetch_prot_override(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot_override);
|
||||
vm_vaddr_t guest_0_page, guest_last_page;
|
||||
|
||||
guest_0_page = vm_vaddr_alloc(t.kvm_vm, PAGE_SIZE, 0);
|
||||
guest_last_page = vm_vaddr_alloc(t.kvm_vm, PAGE_SIZE, last_page_addr);
|
||||
if (guest_0_page != 0 || guest_last_page != last_page_addr) {
|
||||
print_skip("did not allocate guest pages at required positions");
|
||||
goto out;
|
||||
}
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
t.run->s.regs.crs[0] |= CR0_FETCH_PROTECTION_OVERRIDE;
|
||||
t.run->kvm_dirty_regs = KVM_SYNC_CRS;
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vcpu, mismatching keys on fetch, fetch protection override applies */
|
||||
prepare_mem12();
|
||||
MOP(t.vcpu, LOGICAL, WRITE, mem1, PAGE_SIZE, GADDR_V(mem1));
|
||||
HOST_SYNC(t.vcpu, STAGE_COPIED);
|
||||
CHECK_N_DO(MOP, t.vcpu, LOGICAL, READ, mem2, 2048, GADDR_V(guest_0_page), KEY(2));
|
||||
ASSERT_MEM_EQ(mem1, mem2, 2048);
|
||||
|
||||
/*
|
||||
* vcpu, mismatching keys on fetch, fetch protection override applies,
|
||||
* wraparound
|
||||
*/
|
||||
prepare_mem12();
|
||||
MOP(t.vcpu, LOGICAL, WRITE, mem1, 2 * PAGE_SIZE, GADDR_V(guest_last_page));
|
||||
HOST_SYNC(t.vcpu, STAGE_COPIED);
|
||||
CHECK_N_DO(MOP, t.vcpu, LOGICAL, READ, mem2, PAGE_SIZE + 2048,
|
||||
GADDR_V(guest_last_page), KEY(2));
|
||||
ASSERT_MEM_EQ(mem1, mem2, 2048);
|
||||
|
||||
out:
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void test_errors_key_fetch_prot_override_not_enabled(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot_override);
|
||||
vm_vaddr_t guest_0_page, guest_last_page;
|
||||
|
||||
guest_0_page = vm_vaddr_alloc(t.kvm_vm, PAGE_SIZE, 0);
|
||||
guest_last_page = vm_vaddr_alloc(t.kvm_vm, PAGE_SIZE, last_page_addr);
|
||||
if (guest_0_page != 0 || guest_last_page != last_page_addr) {
|
||||
print_skip("did not allocate guest pages at required positions");
|
||||
goto out;
|
||||
}
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/* vcpu, mismatching keys on fetch, fetch protection override not enabled */
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vcpu, LOGICAL, READ, mem2, 2048, GADDR_V(0), KEY(2));
|
||||
|
||||
out:
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void test_errors_key_fetch_prot_override_enabled(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_copy_key_fetch_prot_override);
|
||||
vm_vaddr_t guest_0_page, guest_last_page;
|
||||
|
||||
guest_0_page = vm_vaddr_alloc(t.kvm_vm, PAGE_SIZE, 0);
|
||||
guest_last_page = vm_vaddr_alloc(t.kvm_vm, PAGE_SIZE, last_page_addr);
|
||||
if (guest_0_page != 0 || guest_last_page != last_page_addr) {
|
||||
print_skip("did not allocate guest pages at required positions");
|
||||
goto out;
|
||||
}
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
t.run->s.regs.crs[0] |= CR0_FETCH_PROTECTION_OVERRIDE;
|
||||
t.run->kvm_dirty_regs = KVM_SYNC_CRS;
|
||||
HOST_SYNC(t.vcpu, STAGE_SKEYS_SET);
|
||||
|
||||
/*
|
||||
* vcpu, mismatching keys on fetch,
|
||||
* fetch protection override does not apply because memory range acceeded
|
||||
*/
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vcpu, LOGICAL, READ, mem2, 2048 + 1, GADDR_V(0), KEY(2));
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vcpu, LOGICAL, READ, mem2, PAGE_SIZE + 2048 + 1,
|
||||
GADDR_V(guest_last_page), KEY(2));
|
||||
/* vm, fetch protected override does not apply */
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, READ, mem2, 2048, GADDR(0), KEY(2));
|
||||
CHECK_N_DO(ERR_PROT_MOP, t.vm, ABSOLUTE, READ, mem2, 2048, GADDR_V(guest_0_page), KEY(2));
|
||||
|
||||
out:
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
static void guest_idle(void)
|
||||
{
|
||||
GUEST_SYNC(STAGE_INITED); /* for consistency's sake */
|
||||
for (;;)
|
||||
GUEST_SYNC(STAGE_IDLED);
|
||||
}
|
||||
|
||||
static void _test_errors_common(struct test_vcpu vcpu, enum mop_target target, int size)
|
||||
{
|
||||
int rv;
|
||||
|
||||
/* Bad size: */
|
||||
rv = ERR_MOP(vcpu, target, WRITE, mem1, -1, GADDR_V(mem1));
|
||||
TEST_ASSERT(rv == -1 && errno == E2BIG, "ioctl allows insane sizes");
|
||||
|
||||
/* Zero size: */
|
||||
ksmo.gaddr = (uintptr_t)mem1;
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = 0;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 0;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
rv = ERR_MOP(vcpu, target, WRITE, mem1, 0, GADDR_V(mem1));
|
||||
TEST_ASSERT(rv == -1 && (errno == EINVAL || errno == ENOMEM),
|
||||
"ioctl allows 0 as size");
|
||||
|
||||
/* Bad flags: */
|
||||
ksmo.gaddr = (uintptr_t)mem1;
|
||||
ksmo.flags = -1;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 0;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
rv = ERR_MOP(vcpu, target, WRITE, mem1, size, GADDR_V(mem1), SET_FLAGS(-1));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows all flags");
|
||||
|
||||
/* Bad operation: */
|
||||
ksmo.gaddr = (uintptr_t)mem1;
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = -1;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 0;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows bad operations");
|
||||
|
||||
/* Bad guest address: */
|
||||
ksmo.gaddr = ~0xfffUL;
|
||||
ksmo.flags = KVM_S390_MEMOP_F_CHECK_ONLY;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 0;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
rv = ERR_MOP(vcpu, target, WRITE, mem1, size, GADDR((void *)~0xfffUL), CHECK_ONLY);
|
||||
TEST_ASSERT(rv > 0, "ioctl does not report bad guest memory access");
|
||||
|
||||
/* Bad host address: */
|
||||
ksmo.gaddr = (uintptr_t)mem1;
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = 0;
|
||||
ksmo.ar = 0;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
rv = ERR_MOP(vcpu, target, WRITE, 0, size, GADDR_V(mem1));
|
||||
TEST_ASSERT(rv == -1 && errno == EFAULT,
|
||||
"ioctl does not report bad host memory address");
|
||||
|
||||
/* Bad access register: */
|
||||
run->psw_mask &= ~(3UL << (63 - 17));
|
||||
run->psw_mask |= 1UL << (63 - 17); /* Enable AR mode */
|
||||
vcpu_run(vm, VCPU_ID); /* To sync new state to SIE block */
|
||||
ksmo.gaddr = (uintptr_t)mem1;
|
||||
ksmo.flags = 0;
|
||||
ksmo.size = maxsize;
|
||||
ksmo.op = KVM_S390_MEMOP_LOGICAL_WRITE;
|
||||
ksmo.buf = (uintptr_t)mem1;
|
||||
ksmo.ar = 17;
|
||||
rv = _vcpu_ioctl(vm, VCPU_ID, KVM_S390_MEM_OP, &ksmo);
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows ARs > 15");
|
||||
run->psw_mask &= ~(3UL << (63 - 17)); /* Disable AR mode */
|
||||
vcpu_run(vm, VCPU_ID); /* Run to sync new state */
|
||||
/* Bad key: */
|
||||
rv = ERR_MOP(vcpu, target, WRITE, mem1, size, GADDR_V(mem1), KEY(17));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows invalid key");
|
||||
}
|
||||
|
||||
kvm_vm_free(vm);
|
||||
static void test_errors(void)
|
||||
{
|
||||
struct test_default t = test_default_init(guest_idle);
|
||||
int rv;
|
||||
|
||||
HOST_SYNC(t.vcpu, STAGE_INITED);
|
||||
|
||||
_test_errors_common(t.vcpu, LOGICAL, t.size);
|
||||
_test_errors_common(t.vm, ABSOLUTE, t.size);
|
||||
|
||||
/* Bad operation: */
|
||||
rv = ERR_MOP(t.vcpu, INVALID, WRITE, mem1, t.size, GADDR_V(mem1));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows bad operations");
|
||||
/* virtual addresses are not translated when passing INVALID */
|
||||
rv = ERR_MOP(t.vm, INVALID, WRITE, mem1, PAGE_SIZE, GADDR(0));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows bad operations");
|
||||
|
||||
/* Bad access register: */
|
||||
t.run->psw_mask &= ~(3UL << (63 - 17));
|
||||
t.run->psw_mask |= 1UL << (63 - 17); /* Enable AR mode */
|
||||
HOST_SYNC(t.vcpu, STAGE_IDLED); /* To sync new state to SIE block */
|
||||
rv = ERR_MOP(t.vcpu, LOGICAL, WRITE, mem1, t.size, GADDR_V(mem1), AR(17));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL, "ioctl allows ARs > 15");
|
||||
t.run->psw_mask &= ~(3UL << (63 - 17)); /* Disable AR mode */
|
||||
HOST_SYNC(t.vcpu, STAGE_IDLED); /* Run to sync new state */
|
||||
|
||||
/* Check that the SIDA calls are rejected for non-protected guests */
|
||||
rv = ERR_MOP(t.vcpu, SIDA, READ, mem1, 8, GADDR(0), SIDA_OFFSET(0x1c0));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL,
|
||||
"ioctl does not reject SIDA_READ in non-protected mode");
|
||||
rv = ERR_MOP(t.vcpu, SIDA, WRITE, mem1, 8, GADDR(0), SIDA_OFFSET(0x1c0));
|
||||
TEST_ASSERT(rv == -1 && errno == EINVAL,
|
||||
"ioctl does not reject SIDA_WRITE in non-protected mode");
|
||||
|
||||
kvm_vm_free(t.kvm_vm);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int memop_cap, extension_cap;
|
||||
|
||||
setbuf(stdout, NULL); /* Tell stdout not to buffer its content */
|
||||
|
||||
memop_cap = kvm_check_cap(KVM_CAP_S390_MEM_OP);
|
||||
extension_cap = kvm_check_cap(KVM_CAP_S390_MEM_OP_EXTENSION);
|
||||
if (!memop_cap) {
|
||||
print_skip("CAP_S390_MEM_OP not supported");
|
||||
exit(KSFT_SKIP);
|
||||
}
|
||||
|
||||
test_copy();
|
||||
if (extension_cap > 0) {
|
||||
test_copy_key();
|
||||
test_copy_key_storage_prot_override();
|
||||
test_copy_key_fetch_prot();
|
||||
test_copy_key_fetch_prot_override();
|
||||
test_errors_key();
|
||||
test_errors_key_storage_prot_override();
|
||||
test_errors_key_fetch_prot_override_not_enabled();
|
||||
test_errors_key_fetch_prot_override_enabled();
|
||||
} else {
|
||||
print_skip("storage key memop extension not supported");
|
||||
}
|
||||
test_errors();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
227
tools/testing/selftests/kvm/s390x/tprot.c
Normal file
227
tools/testing/selftests/kvm/s390x/tprot.c
Normal file
@@ -0,0 +1,227 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Test TEST PROTECTION emulation.
|
||||
*
|
||||
* Copyright IBM Corp. 2021
|
||||
*/
|
||||
|
||||
#include <sys/mman.h>
|
||||
#include "test_util.h"
|
||||
#include "kvm_util.h"
|
||||
|
||||
#define PAGE_SHIFT 12
|
||||
#define PAGE_SIZE (1 << PAGE_SHIFT)
|
||||
#define CR0_FETCH_PROTECTION_OVERRIDE (1UL << (63 - 38))
|
||||
#define CR0_STORAGE_PROTECTION_OVERRIDE (1UL << (63 - 39))
|
||||
|
||||
#define VCPU_ID 1
|
||||
|
||||
static __aligned(PAGE_SIZE) uint8_t pages[2][PAGE_SIZE];
|
||||
static uint8_t *const page_store_prot = pages[0];
|
||||
static uint8_t *const page_fetch_prot = pages[1];
|
||||
|
||||
/* Nonzero return value indicates that address not mapped */
|
||||
static int set_storage_key(void *addr, uint8_t key)
|
||||
{
|
||||
int not_mapped = 0;
|
||||
|
||||
asm volatile (
|
||||
"lra %[addr], 0(0,%[addr])\n"
|
||||
" jz 0f\n"
|
||||
" llill %[not_mapped],1\n"
|
||||
" j 1f\n"
|
||||
"0: sske %[key], %[addr]\n"
|
||||
"1:"
|
||||
: [addr] "+&a" (addr), [not_mapped] "+r" (not_mapped)
|
||||
: [key] "r" (key)
|
||||
: "cc"
|
||||
);
|
||||
return -not_mapped;
|
||||
}
|
||||
|
||||
enum permission {
|
||||
READ_WRITE = 0,
|
||||
READ = 1,
|
||||
RW_PROTECTED = 2,
|
||||
TRANSL_UNAVAIL = 3,
|
||||
};
|
||||
|
||||
static enum permission test_protection(void *addr, uint8_t key)
|
||||
{
|
||||
uint64_t mask;
|
||||
|
||||
asm volatile (
|
||||
"tprot %[addr], 0(%[key])\n"
|
||||
" ipm %[mask]\n"
|
||||
: [mask] "=r" (mask)
|
||||
: [addr] "Q" (*(char *)addr),
|
||||
[key] "a" (key)
|
||||
: "cc"
|
||||
);
|
||||
|
||||
return (enum permission)(mask >> 28);
|
||||
}
|
||||
|
||||
enum stage {
|
||||
STAGE_END,
|
||||
STAGE_INIT_SIMPLE,
|
||||
TEST_SIMPLE,
|
||||
STAGE_INIT_FETCH_PROT_OVERRIDE,
|
||||
TEST_FETCH_PROT_OVERRIDE,
|
||||
TEST_STORAGE_PROT_OVERRIDE,
|
||||
};
|
||||
|
||||
struct test {
|
||||
enum stage stage;
|
||||
void *addr;
|
||||
uint8_t key;
|
||||
enum permission expected;
|
||||
} tests[] = {
|
||||
/*
|
||||
* We perform each test in the array by executing TEST PROTECTION on
|
||||
* the specified addr with the specified key and checking if the returned
|
||||
* permissions match the expected value.
|
||||
* Both guest and host cooperate to set up the required test conditions.
|
||||
* A central condition is that the page targeted by addr has to be DAT
|
||||
* protected in the host mappings, in order for KVM to emulate the
|
||||
* TEST PROTECTION instruction.
|
||||
* Since the page tables are shared, the host uses mprotect to achieve
|
||||
* this.
|
||||
*
|
||||
* Test resulting in RW_PROTECTED/TRANSL_UNAVAIL will be interpreted
|
||||
* by SIE, not KVM, but there is no harm in testing them also.
|
||||
* See Enhanced Suppression-on-Protection Facilities in the
|
||||
* Interpretive-Execution Mode
|
||||
*/
|
||||
/*
|
||||
* guest: set storage key of page_store_prot to 1
|
||||
* storage key of page_fetch_prot to 9 and enable
|
||||
* protection for it
|
||||
* STAGE_INIT_SIMPLE
|
||||
* host: write protect both via mprotect
|
||||
*/
|
||||
/* access key 0 matches any storage key -> RW */
|
||||
{ TEST_SIMPLE, page_store_prot, 0x00, READ_WRITE },
|
||||
/* access key matches storage key -> RW */
|
||||
{ TEST_SIMPLE, page_store_prot, 0x10, READ_WRITE },
|
||||
/* mismatched keys, but no fetch protection -> RO */
|
||||
{ TEST_SIMPLE, page_store_prot, 0x20, READ },
|
||||
/* access key 0 matches any storage key -> RW */
|
||||
{ TEST_SIMPLE, page_fetch_prot, 0x00, READ_WRITE },
|
||||
/* access key matches storage key -> RW */
|
||||
{ TEST_SIMPLE, page_fetch_prot, 0x90, READ_WRITE },
|
||||
/* mismatched keys, fetch protection -> inaccessible */
|
||||
{ TEST_SIMPLE, page_fetch_prot, 0x10, RW_PROTECTED },
|
||||
/* page 0 not mapped yet -> translation not available */
|
||||
{ TEST_SIMPLE, (void *)0x00, 0x10, TRANSL_UNAVAIL },
|
||||
/*
|
||||
* host: try to map page 0
|
||||
* guest: set storage key of page 0 to 9 and enable fetch protection
|
||||
* STAGE_INIT_FETCH_PROT_OVERRIDE
|
||||
* host: write protect page 0
|
||||
* enable fetch protection override
|
||||
*/
|
||||
/* mismatched keys, fetch protection, but override applies -> RO */
|
||||
{ TEST_FETCH_PROT_OVERRIDE, (void *)0x00, 0x10, READ },
|
||||
/* mismatched keys, fetch protection, override applies to 0-2048 only -> inaccessible */
|
||||
{ TEST_FETCH_PROT_OVERRIDE, (void *)2049, 0x10, RW_PROTECTED },
|
||||
/*
|
||||
* host: enable storage protection override
|
||||
*/
|
||||
/* mismatched keys, but override applies (storage key 9) -> RW */
|
||||
{ TEST_STORAGE_PROT_OVERRIDE, page_fetch_prot, 0x10, READ_WRITE },
|
||||
/* mismatched keys, no fetch protection, override doesn't apply -> RO */
|
||||
{ TEST_STORAGE_PROT_OVERRIDE, page_store_prot, 0x20, READ },
|
||||
/* mismatched keys, but override applies (storage key 9) -> RW */
|
||||
{ TEST_STORAGE_PROT_OVERRIDE, (void *)2049, 0x10, READ_WRITE },
|
||||
/* end marker */
|
||||
{ STAGE_END, 0, 0, 0 },
|
||||
};
|
||||
|
||||
static enum stage perform_next_stage(int *i, bool mapped_0)
|
||||
{
|
||||
enum stage stage = tests[*i].stage;
|
||||
enum permission result;
|
||||
bool skip;
|
||||
|
||||
for (; tests[*i].stage == stage; (*i)++) {
|
||||
/*
|
||||
* Some fetch protection override tests require that page 0
|
||||
* be mapped, however, when the hosts tries to map that page via
|
||||
* vm_vaddr_alloc, it may happen that some other page gets mapped
|
||||
* instead.
|
||||
* In order to skip these tests we detect this inside the guest
|
||||
*/
|
||||
skip = tests[*i].addr < (void *)4096 &&
|
||||
tests[*i].expected != TRANSL_UNAVAIL &&
|
||||
!mapped_0;
|
||||
if (!skip) {
|
||||
result = test_protection(tests[*i].addr, tests[*i].key);
|
||||
GUEST_ASSERT_2(result == tests[*i].expected, *i, result);
|
||||
}
|
||||
}
|
||||
return stage;
|
||||
}
|
||||
|
||||
static void guest_code(void)
|
||||
{
|
||||
bool mapped_0;
|
||||
int i = 0;
|
||||
|
||||
GUEST_ASSERT_EQ(set_storage_key(page_store_prot, 0x10), 0);
|
||||
GUEST_ASSERT_EQ(set_storage_key(page_fetch_prot, 0x98), 0);
|
||||
GUEST_SYNC(STAGE_INIT_SIMPLE);
|
||||
GUEST_SYNC(perform_next_stage(&i, false));
|
||||
|
||||
/* Fetch-protection override */
|
||||
mapped_0 = !set_storage_key((void *)0, 0x98);
|
||||
GUEST_SYNC(STAGE_INIT_FETCH_PROT_OVERRIDE);
|
||||
GUEST_SYNC(perform_next_stage(&i, mapped_0));
|
||||
|
||||
/* Storage-protection override */
|
||||
GUEST_SYNC(perform_next_stage(&i, mapped_0));
|
||||
}
|
||||
|
||||
#define HOST_SYNC(vmp, stage) \
|
||||
({ \
|
||||
struct kvm_vm *__vm = (vmp); \
|
||||
struct ucall uc; \
|
||||
int __stage = (stage); \
|
||||
\
|
||||
vcpu_run(__vm, VCPU_ID); \
|
||||
get_ucall(__vm, VCPU_ID, &uc); \
|
||||
if (uc.cmd == UCALL_ABORT) { \
|
||||
TEST_FAIL("line %lu: %s, hints: %lu, %lu", uc.args[1], \
|
||||
(const char *)uc.args[0], uc.args[2], uc.args[3]); \
|
||||
} \
|
||||
ASSERT_EQ(uc.cmd, UCALL_SYNC); \
|
||||
ASSERT_EQ(uc.args[1], __stage); \
|
||||
})
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct kvm_vm *vm;
|
||||
struct kvm_run *run;
|
||||
vm_vaddr_t guest_0_page;
|
||||
|
||||
vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
|
||||
HOST_SYNC(vm, STAGE_INIT_SIMPLE);
|
||||
mprotect(addr_gva2hva(vm, (vm_vaddr_t)pages), PAGE_SIZE * 2, PROT_READ);
|
||||
HOST_SYNC(vm, TEST_SIMPLE);
|
||||
|
||||
guest_0_page = vm_vaddr_alloc(vm, PAGE_SIZE, 0);
|
||||
if (guest_0_page != 0)
|
||||
print_skip("Did not allocate page at 0 for fetch protection override tests");
|
||||
HOST_SYNC(vm, STAGE_INIT_FETCH_PROT_OVERRIDE);
|
||||
if (guest_0_page == 0)
|
||||
mprotect(addr_gva2hva(vm, (vm_vaddr_t)0), PAGE_SIZE, PROT_READ);
|
||||
run->s.regs.crs[0] |= CR0_FETCH_PROTECTION_OVERRIDE;
|
||||
run->kvm_dirty_regs = KVM_SYNC_CRS;
|
||||
HOST_SYNC(vm, TEST_FETCH_PROT_OVERRIDE);
|
||||
|
||||
run->s.regs.crs[0] |= CR0_STORAGE_PROTECTION_OVERRIDE;
|
||||
run->kvm_dirty_regs = KVM_SYNC_CRS;
|
||||
HOST_SYNC(vm, TEST_STORAGE_PROT_OVERRIDE);
|
||||
}
|
||||
@@ -329,22 +329,6 @@ static void test_zero_memory_regions(void)
|
||||
}
|
||||
#endif /* __x86_64__ */
|
||||
|
||||
static int test_memory_region_add(struct kvm_vm *vm, void *mem, uint32_t slot,
|
||||
uint32_t size, uint64_t guest_addr)
|
||||
{
|
||||
struct kvm_userspace_memory_region region;
|
||||
int ret;
|
||||
|
||||
region.slot = slot;
|
||||
region.flags = 0;
|
||||
region.guest_phys_addr = guest_addr;
|
||||
region.memory_size = size;
|
||||
region.userspace_addr = (uintptr_t) mem;
|
||||
ret = ioctl(vm_get_fd(vm), KVM_SET_USER_MEMORY_REGION, ®ion);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test it can be added memory slots up to KVM_CAP_NR_MEMSLOTS, then any
|
||||
* tentative to add further slots should fail.
|
||||
@@ -382,23 +366,20 @@ static void test_add_max_memory_regions(void)
|
||||
TEST_ASSERT(mem != MAP_FAILED, "Failed to mmap() host");
|
||||
mem_aligned = (void *)(((size_t) mem + alignment - 1) & ~(alignment - 1));
|
||||
|
||||
for (slot = 0; slot < max_mem_slots; slot++) {
|
||||
ret = test_memory_region_add(vm, mem_aligned +
|
||||
((uint64_t)slot * MEM_REGION_SIZE),
|
||||
slot, MEM_REGION_SIZE,
|
||||
(uint64_t)slot * MEM_REGION_SIZE);
|
||||
TEST_ASSERT(ret == 0, "KVM_SET_USER_MEMORY_REGION IOCTL failed,\n"
|
||||
" rc: %i errno: %i slot: %i\n",
|
||||
ret, errno, slot);
|
||||
}
|
||||
for (slot = 0; slot < max_mem_slots; slot++)
|
||||
vm_set_user_memory_region(vm, slot, 0,
|
||||
((uint64_t)slot * MEM_REGION_SIZE),
|
||||
MEM_REGION_SIZE,
|
||||
mem_aligned + (uint64_t)slot * MEM_REGION_SIZE);
|
||||
|
||||
/* Check it cannot be added memory slots beyond the limit */
|
||||
mem_extra = mmap(NULL, MEM_REGION_SIZE, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
TEST_ASSERT(mem_extra != MAP_FAILED, "Failed to mmap() host");
|
||||
|
||||
ret = test_memory_region_add(vm, mem_extra, max_mem_slots, MEM_REGION_SIZE,
|
||||
(uint64_t)max_mem_slots * MEM_REGION_SIZE);
|
||||
ret = __vm_set_user_memory_region(vm, max_mem_slots, 0,
|
||||
(uint64_t)max_mem_slots * MEM_REGION_SIZE,
|
||||
MEM_REGION_SIZE, mem_extra);
|
||||
TEST_ASSERT(ret == -1 && errno == EINVAL,
|
||||
"Adding one more memory slot should fail with EINVAL");
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/bitmap.h>
|
||||
|
||||
#include "test_util.h"
|
||||
|
||||
@@ -32,6 +33,22 @@ static void guest_nmi_handler(struct ex_regs *regs)
|
||||
{
|
||||
}
|
||||
|
||||
/* Exits to L1 destroy GRPs! */
|
||||
static inline void rdmsr_fs_base(void)
|
||||
{
|
||||
__asm__ __volatile__ ("mov $0xc0000100, %%rcx; rdmsr" : : :
|
||||
"rax", "rbx", "rcx", "rdx",
|
||||
"rsi", "rdi", "r8", "r9", "r10", "r11", "r12",
|
||||
"r13", "r14", "r15");
|
||||
}
|
||||
static inline void rdmsr_gs_base(void)
|
||||
{
|
||||
__asm__ __volatile__ ("mov $0xc0000101, %%rcx; rdmsr" : : :
|
||||
"rax", "rbx", "rcx", "rdx",
|
||||
"rsi", "rdi", "r8", "r9", "r10", "r11", "r12",
|
||||
"r13", "r14", "r15");
|
||||
}
|
||||
|
||||
void l2_guest_code(void)
|
||||
{
|
||||
GUEST_SYNC(7);
|
||||
@@ -41,6 +58,15 @@ void l2_guest_code(void)
|
||||
/* Forced exit to L1 upon restore */
|
||||
GUEST_SYNC(9);
|
||||
|
||||
vmcall();
|
||||
|
||||
/* MSR-Bitmap tests */
|
||||
rdmsr_fs_base(); /* intercepted */
|
||||
rdmsr_fs_base(); /* intercepted */
|
||||
rdmsr_gs_base(); /* not intercepted */
|
||||
vmcall();
|
||||
rdmsr_gs_base(); /* intercepted */
|
||||
|
||||
/* Done, exit to L1 and never come back. */
|
||||
vmcall();
|
||||
}
|
||||
@@ -76,8 +102,9 @@ void guest_code(struct vmx_pages *vmx_pages)
|
||||
current_evmcs->revision_id = EVMCS_VERSION;
|
||||
GUEST_SYNC(6);
|
||||
|
||||
current_evmcs->pin_based_vm_exec_control |=
|
||||
PIN_BASED_NMI_EXITING;
|
||||
vmwrite(PIN_BASED_VM_EXEC_CONTROL, vmreadz(PIN_BASED_VM_EXEC_CONTROL) |
|
||||
PIN_BASED_NMI_EXITING);
|
||||
|
||||
GUEST_ASSERT(!vmlaunch());
|
||||
GUEST_ASSERT(vmptrstz() == vmx_pages->enlightened_vmcs_gpa);
|
||||
|
||||
@@ -90,6 +117,39 @@ void guest_code(struct vmx_pages *vmx_pages)
|
||||
|
||||
GUEST_SYNC(10);
|
||||
|
||||
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
|
||||
current_evmcs->guest_rip += 3; /* vmcall */
|
||||
|
||||
/* Intercept RDMSR 0xc0000100 */
|
||||
vmwrite(CPU_BASED_VM_EXEC_CONTROL, vmreadz(CPU_BASED_VM_EXEC_CONTROL) |
|
||||
CPU_BASED_USE_MSR_BITMAPS);
|
||||
set_bit(MSR_FS_BASE & 0x1fff, vmx_pages->msr + 0x400);
|
||||
GUEST_ASSERT(!vmresume());
|
||||
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_MSR_READ);
|
||||
current_evmcs->guest_rip += 2; /* rdmsr */
|
||||
|
||||
/* Enable enlightened MSR bitmap */
|
||||
current_evmcs->hv_enlightenments_control.msr_bitmap = 1;
|
||||
GUEST_ASSERT(!vmresume());
|
||||
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_MSR_READ);
|
||||
current_evmcs->guest_rip += 2; /* rdmsr */
|
||||
|
||||
/* Intercept RDMSR 0xc0000101 without telling KVM about it */
|
||||
set_bit(MSR_GS_BASE & 0x1fff, vmx_pages->msr + 0x400);
|
||||
/* Make sure HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP is set */
|
||||
current_evmcs->hv_clean_fields |= HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP;
|
||||
GUEST_ASSERT(!vmresume());
|
||||
/* Make sure we don't see EXIT_REASON_MSR_READ here so eMSR bitmap works */
|
||||
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
|
||||
current_evmcs->guest_rip += 3; /* vmcall */
|
||||
|
||||
/* Now tell KVM we've changed MSR-Bitmap */
|
||||
current_evmcs->hv_clean_fields &= ~HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP;
|
||||
GUEST_ASSERT(!vmresume());
|
||||
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_MSR_READ);
|
||||
current_evmcs->guest_rip += 2; /* rdmsr */
|
||||
|
||||
GUEST_ASSERT(!vmresume());
|
||||
GUEST_ASSERT(vmreadz(VM_EXIT_REASON) == EXIT_REASON_VMCALL);
|
||||
GUEST_SYNC(11);
|
||||
|
||||
|
||||
@@ -49,16 +49,13 @@ static void test_hv_cpuid(struct kvm_cpuid2 *hv_cpuid_entries,
|
||||
bool evmcs_expected)
|
||||
{
|
||||
int i;
|
||||
int nent = 9;
|
||||
int nent_expected = 10;
|
||||
u32 test_val;
|
||||
|
||||
if (evmcs_expected)
|
||||
nent += 1; /* 0x4000000A */
|
||||
|
||||
TEST_ASSERT(hv_cpuid_entries->nent == nent,
|
||||
TEST_ASSERT(hv_cpuid_entries->nent == nent_expected,
|
||||
"KVM_GET_SUPPORTED_HV_CPUID should return %d entries"
|
||||
" with evmcs=%d (returned %d)",
|
||||
nent, evmcs_expected, hv_cpuid_entries->nent);
|
||||
" (returned %d)",
|
||||
nent_expected, hv_cpuid_entries->nent);
|
||||
|
||||
for (i = 0; i < hv_cpuid_entries->nent; i++) {
|
||||
struct kvm_cpuid_entry2 *entry = &hv_cpuid_entries->entries[i];
|
||||
@@ -68,9 +65,6 @@ static void test_hv_cpuid(struct kvm_cpuid2 *hv_cpuid_entries,
|
||||
"function %x is our of supported range",
|
||||
entry->function);
|
||||
|
||||
TEST_ASSERT(evmcs_expected || (entry->function != 0x4000000A),
|
||||
"0x4000000A leaf should not be reported");
|
||||
|
||||
TEST_ASSERT(entry->index == 0,
|
||||
".index field should be zero");
|
||||
|
||||
@@ -97,8 +91,20 @@ static void test_hv_cpuid(struct kvm_cpuid2 *hv_cpuid_entries,
|
||||
"NoNonArchitecturalCoreSharing bit"
|
||||
" doesn't reflect SMT setting");
|
||||
break;
|
||||
}
|
||||
case 0x4000000A:
|
||||
TEST_ASSERT(entry->eax & (1UL << 19),
|
||||
"Enlightened MSR-Bitmap should always be supported"
|
||||
" 0x40000000.EAX: %x", entry->eax);
|
||||
if (evmcs_expected)
|
||||
TEST_ASSERT((entry->eax & 0xffff) == 0x101,
|
||||
"Supported Enlightened VMCS version range is supposed to be 1:1"
|
||||
" 0x40000000.EAX: %x", entry->eax);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
/*
|
||||
* If needed for debug:
|
||||
* fprintf(stdout,
|
||||
@@ -107,7 +113,6 @@ static void test_hv_cpuid(struct kvm_cpuid2 *hv_cpuid_entries,
|
||||
* entry->edx);
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void test_hv_cpuid_e2big(struct kvm_vm *vm, bool system)
|
||||
|
||||
175
tools/testing/selftests/kvm/x86_64/hyperv_svm_test.c
Normal file
175
tools/testing/selftests/kvm/x86_64/hyperv_svm_test.c
Normal file
@@ -0,0 +1,175 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* KVM_GET/SET_* tests
|
||||
*
|
||||
* Copyright (C) 2022, Red Hat, Inc.
|
||||
*
|
||||
* Tests for Hyper-V extensions to SVM.
|
||||
*/
|
||||
#define _GNU_SOURCE /* for program_invocation_short_name */
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/bitmap.h>
|
||||
|
||||
#include "test_util.h"
|
||||
|
||||
#include "kvm_util.h"
|
||||
#include "processor.h"
|
||||
#include "svm_util.h"
|
||||
#include "hyperv.h"
|
||||
|
||||
#define VCPU_ID 1
|
||||
#define L2_GUEST_STACK_SIZE 256
|
||||
|
||||
struct hv_enlightenments {
|
||||
struct __packed hv_enlightenments_control {
|
||||
u32 nested_flush_hypercall:1;
|
||||
u32 msr_bitmap:1;
|
||||
u32 enlightened_npt_tlb: 1;
|
||||
u32 reserved:29;
|
||||
} __packed hv_enlightenments_control;
|
||||
u32 hv_vp_id;
|
||||
u64 hv_vm_id;
|
||||
u64 partition_assist_page;
|
||||
u64 reserved;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Hyper-V uses the software reserved clean bit in VMCB
|
||||
*/
|
||||
#define VMCB_HV_NESTED_ENLIGHTENMENTS (1U << 31)
|
||||
|
||||
static inline void vmmcall(void)
|
||||
{
|
||||
__asm__ __volatile__("vmmcall");
|
||||
}
|
||||
|
||||
void l2_guest_code(void)
|
||||
{
|
||||
GUEST_SYNC(3);
|
||||
/* Exit to L1 */
|
||||
vmmcall();
|
||||
|
||||
/* MSR-Bitmap tests */
|
||||
rdmsr(MSR_FS_BASE); /* intercepted */
|
||||
rdmsr(MSR_FS_BASE); /* intercepted */
|
||||
rdmsr(MSR_GS_BASE); /* not intercepted */
|
||||
vmmcall();
|
||||
rdmsr(MSR_GS_BASE); /* intercepted */
|
||||
|
||||
GUEST_SYNC(5);
|
||||
|
||||
/* Done, exit to L1 and never come back. */
|
||||
vmmcall();
|
||||
}
|
||||
|
||||
static void __attribute__((__flatten__)) guest_code(struct svm_test_data *svm)
|
||||
{
|
||||
unsigned long l2_guest_stack[L2_GUEST_STACK_SIZE];
|
||||
struct vmcb *vmcb = svm->vmcb;
|
||||
struct hv_enlightenments *hve =
|
||||
(struct hv_enlightenments *)vmcb->control.reserved_sw;
|
||||
|
||||
GUEST_SYNC(1);
|
||||
|
||||
wrmsr(HV_X64_MSR_GUEST_OS_ID, (u64)0x8100 << 48);
|
||||
|
||||
GUEST_ASSERT(svm->vmcb_gpa);
|
||||
/* Prepare for L2 execution. */
|
||||
generic_svm_setup(svm, l2_guest_code,
|
||||
&l2_guest_stack[L2_GUEST_STACK_SIZE]);
|
||||
|
||||
GUEST_SYNC(2);
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL);
|
||||
GUEST_SYNC(4);
|
||||
vmcb->save.rip += 3;
|
||||
|
||||
/* Intercept RDMSR 0xc0000100 */
|
||||
vmcb->control.intercept |= 1ULL << INTERCEPT_MSR_PROT;
|
||||
set_bit(2 * (MSR_FS_BASE & 0x1fff), svm->msr + 0x800);
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR);
|
||||
vmcb->save.rip += 2; /* rdmsr */
|
||||
|
||||
/* Enable enlightened MSR bitmap */
|
||||
hve->hv_enlightenments_control.msr_bitmap = 1;
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR);
|
||||
vmcb->save.rip += 2; /* rdmsr */
|
||||
|
||||
/* Intercept RDMSR 0xc0000101 without telling KVM about it */
|
||||
set_bit(2 * (MSR_GS_BASE & 0x1fff), svm->msr + 0x800);
|
||||
/* Make sure HV_VMX_ENLIGHTENED_CLEAN_FIELD_MSR_BITMAP is set */
|
||||
vmcb->control.clean |= VMCB_HV_NESTED_ENLIGHTENMENTS;
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
/* Make sure we don't see SVM_EXIT_MSR here so eMSR bitmap works */
|
||||
GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL);
|
||||
vmcb->save.rip += 3; /* vmcall */
|
||||
|
||||
/* Now tell KVM we've changed MSR-Bitmap */
|
||||
vmcb->control.clean &= ~VMCB_HV_NESTED_ENLIGHTENMENTS;
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_MSR);
|
||||
vmcb->save.rip += 2; /* rdmsr */
|
||||
|
||||
run_guest(vmcb, svm->vmcb_gpa);
|
||||
GUEST_ASSERT(vmcb->control.exit_code == SVM_EXIT_VMMCALL);
|
||||
GUEST_SYNC(6);
|
||||
|
||||
GUEST_DONE();
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
vm_vaddr_t nested_gva = 0;
|
||||
|
||||
struct kvm_vm *vm;
|
||||
struct kvm_run *run;
|
||||
struct ucall uc;
|
||||
int stage;
|
||||
|
||||
if (!nested_svm_supported()) {
|
||||
print_skip("Nested SVM not supported");
|
||||
exit(KSFT_SKIP);
|
||||
}
|
||||
/* Create VM */
|
||||
vm = vm_create_default(VCPU_ID, 0, guest_code);
|
||||
vcpu_set_hv_cpuid(vm, VCPU_ID);
|
||||
run = vcpu_state(vm, VCPU_ID);
|
||||
vcpu_alloc_svm(vm, &nested_gva);
|
||||
vcpu_args_set(vm, VCPU_ID, 1, nested_gva);
|
||||
|
||||
for (stage = 1;; stage++) {
|
||||
_vcpu_run(vm, VCPU_ID);
|
||||
TEST_ASSERT(run->exit_reason == KVM_EXIT_IO,
|
||||
"Stage %d: unexpected exit reason: %u (%s),\n",
|
||||
stage, run->exit_reason,
|
||||
exit_reason_str(run->exit_reason));
|
||||
|
||||
switch (get_ucall(vm, VCPU_ID, &uc)) {
|
||||
case UCALL_ABORT:
|
||||
TEST_FAIL("%s at %s:%ld", (const char *)uc.args[0],
|
||||
__FILE__, uc.args[1]);
|
||||
/* NOT REACHED */
|
||||
case UCALL_SYNC:
|
||||
break;
|
||||
case UCALL_DONE:
|
||||
goto done;
|
||||
default:
|
||||
TEST_FAIL("Unknown ucall %lu", uc.cmd);
|
||||
}
|
||||
|
||||
/* UCALL_SYNC is handled here. */
|
||||
TEST_ASSERT(!strcmp((const char *)uc.args[0], "hello") &&
|
||||
uc.args[1] == stage, "Stage %d: Unexpected register values vmexit, got %lx",
|
||||
stage, (ulong)uc.args[1]);
|
||||
|
||||
}
|
||||
|
||||
done:
|
||||
kvm_vm_free(vm);
|
||||
}
|
||||
@@ -325,6 +325,37 @@ static void test_not_member_allow_list(struct kvm_vm *vm)
|
||||
TEST_ASSERT(!count, "Disallowed PMU Event is counting");
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that setting KVM_PMU_CAP_DISABLE prevents the use of the PMU.
|
||||
*
|
||||
* Note that KVM_CAP_PMU_CAPABILITY must be invoked prior to creating VCPUs.
|
||||
*/
|
||||
static void test_pmu_config_disable(void (*guest_code)(void))
|
||||
{
|
||||
int r;
|
||||
struct kvm_vm *vm;
|
||||
struct kvm_enable_cap cap = { 0 };
|
||||
|
||||
r = kvm_check_cap(KVM_CAP_PMU_CAPABILITY);
|
||||
if (!(r & KVM_PMU_CAP_DISABLE))
|
||||
return;
|
||||
|
||||
vm = vm_create_without_vcpus(VM_MODE_DEFAULT, DEFAULT_GUEST_PHY_PAGES);
|
||||
|
||||
cap.cap = KVM_CAP_PMU_CAPABILITY;
|
||||
cap.args[0] = KVM_PMU_CAP_DISABLE;
|
||||
TEST_ASSERT(!vm_enable_cap(vm, &cap), "Failed to set KVM_PMU_CAP_DISABLE.");
|
||||
|
||||
vm_vcpu_add_default(vm, VCPU_ID, guest_code);
|
||||
vm_init_descriptor_tables(vm);
|
||||
vcpu_init_descriptor_tables(vm, VCPU_ID);
|
||||
|
||||
TEST_ASSERT(!sanity_check_pmu(vm),
|
||||
"Guest should not be able to use disabled PMU.");
|
||||
|
||||
kvm_vm_free(vm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for a non-zero PMU version, at least one general-purpose
|
||||
* counter per logical processor, an EBX bit vector of length greater
|
||||
@@ -430,5 +461,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
kvm_vm_free(vm);
|
||||
|
||||
test_pmu_config_disable(guest_code);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#define NR_LOCK_TESTING_THREADS 3
|
||||
#define NR_LOCK_TESTING_ITERATIONS 10000
|
||||
|
||||
bool have_sev_es;
|
||||
|
||||
static int __sev_ioctl(int vm_fd, int cmd_id, void *data, __u32 *fw_error)
|
||||
{
|
||||
struct kvm_sev_cmd cmd = {
|
||||
@@ -172,10 +174,18 @@ static void test_sev_migrate_parameters(void)
|
||||
*sev_es_vm_no_vmsa;
|
||||
int ret;
|
||||
|
||||
sev_vm = sev_vm_create(/* es= */ false);
|
||||
sev_es_vm = sev_vm_create(/* es= */ true);
|
||||
vm_no_vcpu = vm_create(VM_MODE_DEFAULT, 0, O_RDWR);
|
||||
vm_no_sev = aux_vm_create(true);
|
||||
ret = __sev_migrate_from(vm_no_vcpu->fd, vm_no_sev->fd);
|
||||
TEST_ASSERT(ret == -1 && errno == EINVAL,
|
||||
"Migrations require SEV enabled. ret %d, errno: %d\n", ret,
|
||||
errno);
|
||||
|
||||
if (!have_sev_es)
|
||||
goto out;
|
||||
|
||||
sev_vm = sev_vm_create(/* es= */ false);
|
||||
sev_es_vm = sev_vm_create(/* es= */ true);
|
||||
sev_es_vm_no_vmsa = vm_create(VM_MODE_DEFAULT, 0, O_RDWR);
|
||||
sev_ioctl(sev_es_vm_no_vmsa->fd, KVM_SEV_ES_INIT, NULL);
|
||||
vm_vcpu_add(sev_es_vm_no_vmsa, 1);
|
||||
@@ -204,14 +214,10 @@ static void test_sev_migrate_parameters(void)
|
||||
"SEV-ES migrations require UPDATE_VMSA. ret %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
ret = __sev_migrate_from(vm_no_vcpu->fd, vm_no_sev->fd);
|
||||
TEST_ASSERT(ret == -1 && errno == EINVAL,
|
||||
"Migrations require SEV enabled. ret %d, errno: %d\n", ret,
|
||||
errno);
|
||||
|
||||
kvm_vm_free(sev_vm);
|
||||
kvm_vm_free(sev_es_vm);
|
||||
kvm_vm_free(sev_es_vm_no_vmsa);
|
||||
out:
|
||||
kvm_vm_free(vm_no_vcpu);
|
||||
kvm_vm_free(vm_no_sev);
|
||||
}
|
||||
@@ -300,7 +306,6 @@ static void test_sev_mirror_parameters(void)
|
||||
int ret;
|
||||
|
||||
sev_vm = sev_vm_create(/* es= */ false);
|
||||
sev_es_vm = sev_vm_create(/* es= */ true);
|
||||
vm_with_vcpu = aux_vm_create(true);
|
||||
vm_no_vcpu = aux_vm_create(false);
|
||||
|
||||
@@ -310,18 +315,6 @@ static void test_sev_mirror_parameters(void)
|
||||
"Should not be able copy context to self. ret: %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
ret = __sev_mirror_create(sev_vm->fd, sev_es_vm->fd);
|
||||
TEST_ASSERT(
|
||||
ret == -1 && errno == EINVAL,
|
||||
"Should not be able copy context to SEV enabled VM. ret: %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
ret = __sev_mirror_create(sev_es_vm->fd, sev_vm->fd);
|
||||
TEST_ASSERT(
|
||||
ret == -1 && errno == EINVAL,
|
||||
"Should not be able copy context to SEV-ES enabled VM. ret: %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
ret = __sev_mirror_create(vm_no_vcpu->fd, vm_with_vcpu->fd);
|
||||
TEST_ASSERT(ret == -1 && errno == EINVAL,
|
||||
"Copy context requires SEV enabled. ret %d, errno: %d\n", ret,
|
||||
@@ -333,52 +326,113 @@ static void test_sev_mirror_parameters(void)
|
||||
"SEV copy context requires no vCPUS on the destination. ret: %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
kvm_vm_free(sev_vm);
|
||||
if (!have_sev_es)
|
||||
goto out;
|
||||
|
||||
sev_es_vm = sev_vm_create(/* es= */ true);
|
||||
ret = __sev_mirror_create(sev_vm->fd, sev_es_vm->fd);
|
||||
TEST_ASSERT(
|
||||
ret == -1 && errno == EINVAL,
|
||||
"Should not be able copy context to SEV enabled VM. ret: %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
ret = __sev_mirror_create(sev_es_vm->fd, sev_vm->fd);
|
||||
TEST_ASSERT(
|
||||
ret == -1 && errno == EINVAL,
|
||||
"Should not be able copy context to SEV-ES enabled VM. ret: %d, errno: %d\n",
|
||||
ret, errno);
|
||||
|
||||
kvm_vm_free(sev_es_vm);
|
||||
|
||||
out:
|
||||
kvm_vm_free(sev_vm);
|
||||
kvm_vm_free(vm_with_vcpu);
|
||||
kvm_vm_free(vm_no_vcpu);
|
||||
}
|
||||
|
||||
static void test_sev_move_copy(void)
|
||||
{
|
||||
struct kvm_vm *dst_vm, *sev_vm, *mirror_vm, *dst_mirror_vm;
|
||||
int ret;
|
||||
struct kvm_vm *dst_vm, *dst2_vm, *dst3_vm, *sev_vm, *mirror_vm,
|
||||
*dst_mirror_vm, *dst2_mirror_vm, *dst3_mirror_vm;
|
||||
|
||||
sev_vm = sev_vm_create(/* es= */ false);
|
||||
dst_vm = aux_vm_create(true);
|
||||
dst2_vm = aux_vm_create(true);
|
||||
dst3_vm = aux_vm_create(true);
|
||||
mirror_vm = aux_vm_create(false);
|
||||
dst_mirror_vm = aux_vm_create(false);
|
||||
dst2_mirror_vm = aux_vm_create(false);
|
||||
dst3_mirror_vm = aux_vm_create(false);
|
||||
|
||||
sev_mirror_create(mirror_vm->fd, sev_vm->fd);
|
||||
|
||||
sev_migrate_from(dst_mirror_vm->fd, mirror_vm->fd);
|
||||
sev_migrate_from(dst_vm->fd, sev_vm->fd);
|
||||
|
||||
sev_migrate_from(dst2_vm->fd, dst_vm->fd);
|
||||
sev_migrate_from(dst2_mirror_vm->fd, dst_mirror_vm->fd);
|
||||
|
||||
sev_migrate_from(dst3_mirror_vm->fd, dst2_mirror_vm->fd);
|
||||
sev_migrate_from(dst3_vm->fd, dst2_vm->fd);
|
||||
|
||||
kvm_vm_free(dst_vm);
|
||||
kvm_vm_free(sev_vm);
|
||||
kvm_vm_free(dst2_vm);
|
||||
kvm_vm_free(dst3_vm);
|
||||
kvm_vm_free(mirror_vm);
|
||||
kvm_vm_free(dst_mirror_vm);
|
||||
kvm_vm_free(dst2_mirror_vm);
|
||||
kvm_vm_free(dst3_mirror_vm);
|
||||
|
||||
/*
|
||||
* Run similar test be destroy mirrors before mirrored VMs to ensure
|
||||
* destruction is done safely.
|
||||
*/
|
||||
sev_vm = sev_vm_create(/* es= */ false);
|
||||
dst_vm = aux_vm_create(true);
|
||||
mirror_vm = aux_vm_create(false);
|
||||
dst_mirror_vm = aux_vm_create(false);
|
||||
|
||||
sev_mirror_create(mirror_vm->fd, sev_vm->fd);
|
||||
ret = __sev_migrate_from(dst_vm->fd, sev_vm->fd);
|
||||
TEST_ASSERT(ret == -1 && errno == EBUSY,
|
||||
"Cannot migrate VM that has mirrors. ret %d, errno: %d\n", ret,
|
||||
errno);
|
||||
|
||||
/* The mirror itself can be migrated. */
|
||||
sev_migrate_from(dst_mirror_vm->fd, mirror_vm->fd);
|
||||
ret = __sev_migrate_from(dst_vm->fd, sev_vm->fd);
|
||||
TEST_ASSERT(ret == -1 && errno == EBUSY,
|
||||
"Cannot migrate VM that has mirrors. ret %d, errno: %d\n", ret,
|
||||
errno);
|
||||
|
||||
/*
|
||||
* mirror_vm is not a mirror anymore, dst_mirror_vm is. Thus,
|
||||
* the owner can be copied as soon as dst_mirror_vm is gone.
|
||||
*/
|
||||
kvm_vm_free(dst_mirror_vm);
|
||||
sev_migrate_from(dst_vm->fd, sev_vm->fd);
|
||||
|
||||
kvm_vm_free(mirror_vm);
|
||||
kvm_vm_free(dst_mirror_vm);
|
||||
kvm_vm_free(dst_vm);
|
||||
kvm_vm_free(sev_vm);
|
||||
}
|
||||
|
||||
#define X86_FEATURE_SEV (1 << 1)
|
||||
#define X86_FEATURE_SEV_ES (1 << 3)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct kvm_cpuid_entry2 *cpuid;
|
||||
|
||||
if (!kvm_check_cap(KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM) &&
|
||||
!kvm_check_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) {
|
||||
print_skip("Capabilities not available");
|
||||
exit(KSFT_SKIP);
|
||||
}
|
||||
|
||||
cpuid = kvm_get_supported_cpuid_entry(0x80000000);
|
||||
if (cpuid->eax < 0x8000001f) {
|
||||
print_skip("AMD memory encryption not available");
|
||||
exit(KSFT_SKIP);
|
||||
}
|
||||
cpuid = kvm_get_supported_cpuid_entry(0x8000001f);
|
||||
if (!(cpuid->eax & X86_FEATURE_SEV)) {
|
||||
print_skip("AMD SEV not available");
|
||||
exit(KSFT_SKIP);
|
||||
}
|
||||
have_sev_es = !!(cpuid->eax & X86_FEATURE_SEV_ES);
|
||||
|
||||
if (kvm_check_cap(KVM_CAP_VM_MOVE_ENC_CONTEXT_FROM)) {
|
||||
test_sev_migrate_from(/* es= */ false);
|
||||
test_sev_migrate_from(/* es= */ true);
|
||||
if (have_sev_es)
|
||||
test_sev_migrate_from(/* es= */ true);
|
||||
test_sev_migrate_locking();
|
||||
test_sev_migrate_parameters();
|
||||
if (kvm_check_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM))
|
||||
@@ -386,7 +440,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
if (kvm_check_cap(KVM_CAP_VM_COPY_ENC_CONTEXT_FROM)) {
|
||||
test_sev_mirror(/* es= */ false);
|
||||
test_sev_mirror(/* es= */ true);
|
||||
if (have_sev_es)
|
||||
test_sev_mirror(/* es= */ true);
|
||||
test_sev_mirror_parameters();
|
||||
}
|
||||
return 0;
|
||||
|
||||
150
tools/testing/selftests/kvm/x86_64/xapic_state_test.c
Normal file
150
tools/testing/selftests/kvm/x86_64/xapic_state_test.c
Normal file
@@ -0,0 +1,150 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#define _GNU_SOURCE /* for program_invocation_short_name */
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include "apic.h"
|
||||
#include "kvm_util.h"
|
||||
#include "processor.h"
|
||||
#include "test_util.h"
|
||||
|
||||
struct kvm_vcpu {
|
||||
uint32_t id;
|
||||
bool is_x2apic;
|
||||
};
|
||||
|
||||
static void xapic_guest_code(void)
|
||||
{
|
||||
asm volatile("cli");
|
||||
|
||||
xapic_enable();
|
||||
|
||||
while (1) {
|
||||
uint64_t val = (u64)xapic_read_reg(APIC_IRR) |
|
||||
(u64)xapic_read_reg(APIC_IRR + 0x10) << 32;
|
||||
|
||||
xapic_write_reg(APIC_ICR2, val >> 32);
|
||||
xapic_write_reg(APIC_ICR, val);
|
||||
GUEST_SYNC(val);
|
||||
}
|
||||
}
|
||||
|
||||
static void x2apic_guest_code(void)
|
||||
{
|
||||
asm volatile("cli");
|
||||
|
||||
x2apic_enable();
|
||||
|
||||
do {
|
||||
uint64_t val = x2apic_read_reg(APIC_IRR) |
|
||||
x2apic_read_reg(APIC_IRR + 0x10) << 32;
|
||||
|
||||
x2apic_write_reg(APIC_ICR, val);
|
||||
GUEST_SYNC(val);
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static void ____test_icr(struct kvm_vm *vm, struct kvm_vcpu *vcpu, uint64_t val)
|
||||
{
|
||||
struct kvm_lapic_state xapic;
|
||||
struct ucall uc;
|
||||
uint64_t icr;
|
||||
|
||||
/*
|
||||
* Tell the guest what ICR value to write. Use the IRR to pass info,
|
||||
* all bits are valid and should not be modified by KVM (ignoring the
|
||||
* fact that vectors 0-15 are technically illegal).
|
||||
*/
|
||||
vcpu_ioctl(vm, vcpu->id, KVM_GET_LAPIC, &xapic);
|
||||
*((u32 *)&xapic.regs[APIC_IRR]) = val;
|
||||
*((u32 *)&xapic.regs[APIC_IRR + 0x10]) = val >> 32;
|
||||
vcpu_ioctl(vm, vcpu->id, KVM_SET_LAPIC, &xapic);
|
||||
|
||||
vcpu_run(vm, vcpu->id);
|
||||
ASSERT_EQ(get_ucall(vm, vcpu->id, &uc), UCALL_SYNC);
|
||||
ASSERT_EQ(uc.args[1], val);
|
||||
|
||||
vcpu_ioctl(vm, vcpu->id, KVM_GET_LAPIC, &xapic);
|
||||
icr = (u64)(*((u32 *)&xapic.regs[APIC_ICR])) |
|
||||
(u64)(*((u32 *)&xapic.regs[APIC_ICR2])) << 32;
|
||||
if (!vcpu->is_x2apic)
|
||||
val &= (-1u | (0xffull << (32 + 24)));
|
||||
ASSERT_EQ(icr, val & ~APIC_ICR_BUSY);
|
||||
}
|
||||
|
||||
static void __test_icr(struct kvm_vm *vm, struct kvm_vcpu *vcpu, uint64_t val)
|
||||
{
|
||||
____test_icr(vm, vcpu, val | APIC_ICR_BUSY);
|
||||
____test_icr(vm, vcpu, val & ~(u64)APIC_ICR_BUSY);
|
||||
}
|
||||
|
||||
static void test_icr(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
|
||||
{
|
||||
uint64_t icr, i, j;
|
||||
|
||||
icr = APIC_DEST_SELF | APIC_INT_ASSERT | APIC_DM_FIXED;
|
||||
for (i = 0; i <= 0xff; i++)
|
||||
__test_icr(vm, vcpu, icr | i);
|
||||
|
||||
icr = APIC_INT_ASSERT | APIC_DM_FIXED;
|
||||
for (i = 0; i <= 0xff; i++)
|
||||
__test_icr(vm, vcpu, icr | i);
|
||||
|
||||
/*
|
||||
* Send all flavors of IPIs to non-existent vCPUs. TODO: use number of
|
||||
* vCPUs, not vcpu.id + 1. Arbitrarily use vector 0xff.
|
||||
*/
|
||||
icr = APIC_INT_ASSERT | 0xff;
|
||||
for (i = vcpu->id + 1; i < 0xff; i++) {
|
||||
for (j = 0; j < 8; j++)
|
||||
__test_icr(vm, vcpu, i << (32 + 24) | APIC_INT_ASSERT | (j << 8));
|
||||
}
|
||||
|
||||
/* And again with a shorthand destination for all types of IPIs. */
|
||||
icr = APIC_DEST_ALLBUT | APIC_INT_ASSERT;
|
||||
for (i = 0; i < 8; i++)
|
||||
__test_icr(vm, vcpu, icr | (i << 8));
|
||||
|
||||
/* And a few garbage value, just make sure it's an IRQ (blocked). */
|
||||
__test_icr(vm, vcpu, 0xa5a5a5a5a5a5a5a5 & ~APIC_DM_FIXED_MASK);
|
||||
__test_icr(vm, vcpu, 0x5a5a5a5a5a5a5a5a & ~APIC_DM_FIXED_MASK);
|
||||
__test_icr(vm, vcpu, -1ull & ~APIC_DM_FIXED_MASK);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct kvm_vcpu vcpu = {
|
||||
.id = 0,
|
||||
.is_x2apic = true,
|
||||
};
|
||||
struct kvm_cpuid2 *cpuid;
|
||||
struct kvm_vm *vm;
|
||||
int i;
|
||||
|
||||
vm = vm_create_default(vcpu.id, 0, x2apic_guest_code);
|
||||
test_icr(vm, &vcpu);
|
||||
kvm_vm_free(vm);
|
||||
|
||||
/*
|
||||
* Use a second VM for the xAPIC test so that x2APIC can be hidden from
|
||||
* the guest in order to test AVIC. KVM disallows changing CPUID after
|
||||
* KVM_RUN and AVIC is disabled if _any_ vCPU is allowed to use x2APIC.
|
||||
*/
|
||||
vm = vm_create_default(vcpu.id, 0, xapic_guest_code);
|
||||
vcpu.is_x2apic = false;
|
||||
|
||||
cpuid = vcpu_get_cpuid(vm, vcpu.id);
|
||||
for (i = 0; i < cpuid->nent; i++) {
|
||||
if (cpuid->entries[i].function == 1)
|
||||
break;
|
||||
}
|
||||
cpuid->entries[i].ecx &= ~BIT(21);
|
||||
vcpu_set_cpuid(vm, vcpu.id, cpuid);
|
||||
|
||||
virt_pg_map(vm, APIC_DEFAULT_GPA, APIC_DEFAULT_GPA);
|
||||
test_icr(vm, &vcpu);
|
||||
kvm_vm_free(vm);
|
||||
}
|
||||
Reference in New Issue
Block a user