From 1b6c1091cea553873de8badece3d916cdc5f3483 Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 27 Jul 2017 16:05:06 +0200 Subject: [PATCH] Update kvm-unit-test from upstream --- tests/kvm-unit-tests/.gitignore | 3 + tests/kvm-unit-tests/COPYRIGHT | 10 +- tests/kvm-unit-tests/MAINTAINERS | 7 + tests/kvm-unit-tests/Makefile | 48 +- tests/kvm-unit-tests/configure | 82 +- tests/kvm-unit-tests/lib/alloc.c | 10 +- tests/kvm-unit-tests/lib/argv.c | 23 +- tests/kvm-unit-tests/lib/asm-generic/io.h | 4 +- tests/kvm-unit-tests/lib/asm-generic/page.h | 4 +- .../kvm-unit-tests/lib/asm-generic/spinlock.h | 16 +- tests/kvm-unit-tests/lib/auxinfo.h | 4 + tests/kvm-unit-tests/lib/bitops.h | 6 +- tests/kvm-unit-tests/lib/errata.h | 27 +- tests/kvm-unit-tests/lib/libcflat.h | 37 +- tests/kvm-unit-tests/lib/pci-edu.c | 2 +- tests/kvm-unit-tests/lib/pci-testdev.c | 4 +- tests/kvm-unit-tests/lib/pci.c | 20 +- tests/kvm-unit-tests/lib/pci.h | 3 + tests/kvm-unit-tests/lib/printf.c | 75 +- tests/kvm-unit-tests/lib/report.c | 51 +- tests/kvm-unit-tests/lib/setjmp.h | 6 + tests/kvm-unit-tests/lib/stack.c | 7 + tests/kvm-unit-tests/lib/stack.h | 6 + tests/kvm-unit-tests/lib/string.c | 48 + tests/kvm-unit-tests/lib/string.h | 6 + tests/kvm-unit-tests/lib/util.c | 1 + tests/kvm-unit-tests/lib/virtio-mmio.c | 4 +- tests/kvm-unit-tests/lib/virtio-mmio.h | 4 +- tests/kvm-unit-tests/lib/virtio.c | 4 +- tests/kvm-unit-tests/lib/virtio.h | 4 +- tests/kvm-unit-tests/lib/x86/asm/page.h | 2 + tests/kvm-unit-tests/lib/x86/asm/spinlock.h | 7 +- tests/kvm-unit-tests/lib/x86/desc.c | 32 +- tests/kvm-unit-tests/lib/x86/desc.h | 63 +- tests/kvm-unit-tests/lib/x86/fwcfg.c | 2 +- tests/kvm-unit-tests/lib/x86/intel-iommu.c | 16 +- tests/kvm-unit-tests/lib/x86/processor.h | 24 +- tests/kvm-unit-tests/lib/x86/smp.c | 37 +- tests/kvm-unit-tests/lib/x86/smp.h | 2 + tests/kvm-unit-tests/lib/x86/vm.c | 183 +- tests/kvm-unit-tests/lib/x86/vm.h | 28 + tests/kvm-unit-tests/x86/Makefile | 2 +- tests/kvm-unit-tests/x86/Makefile.common | 44 +- tests/kvm-unit-tests/x86/Makefile.i386 | 2 +- tests/kvm-unit-tests/x86/Makefile.x86_64 | 5 +- tests/kvm-unit-tests/x86/asyncpf.c | 6 +- tests/kvm-unit-tests/x86/cstart.S | 14 +- tests/kvm-unit-tests/x86/cstart64.S | 12 +- tests/kvm-unit-tests/x86/eventinj.c | 2 +- tests/kvm-unit-tests/x86/hyperv.c | 65 +- tests/kvm-unit-tests/x86/hyperv.h | 40 +- tests/kvm-unit-tests/x86/hyperv_clock.c | 29 +- tests/kvm-unit-tests/x86/hyperv_stimer.c | 83 +- tests/kvm-unit-tests/x86/hyperv_synic.c | 46 +- tests/kvm-unit-tests/x86/kvmclock_test.c | 33 +- tests/kvm-unit-tests/x86/msr.c | 29 +- tests/kvm-unit-tests/x86/pmu.c | 2 +- tests/kvm-unit-tests/x86/run | 51 - tests/kvm-unit-tests/x86/tsc.c | 2 +- tests/kvm-unit-tests/x86/unittests.cfg | 226 --- tests/kvm-unit-tests/x86/vmexit.c | 42 +- tests/kvm-unit-tests/x86/vmx.c | 990 ++++++++-- tests/kvm-unit-tests/x86/vmx.h | 217 +- tests/kvm-unit-tests/x86/vmx_tests.c | 1741 ++++++++++++++++- tests/kvm-unit-tests/x86/xsave.c | 2 +- 65 files changed, 3718 insertions(+), 889 deletions(-) delete mode 100755 tests/kvm-unit-tests/x86/run delete mode 100644 tests/kvm-unit-tests/x86/unittests.cfg diff --git a/tests/kvm-unit-tests/.gitignore b/tests/kvm-unit-tests/.gitignore index 2213b9b1..2405a808 100644 --- a/tests/kvm-unit-tests/.gitignore +++ b/tests/kvm-unit-tests/.gitignore @@ -17,3 +17,6 @@ cscope.* /build-head /logs/ /logs.old/ +/api/api-sample +/api/dirty-log +/api/dirty-log-perf diff --git a/tests/kvm-unit-tests/COPYRIGHT b/tests/kvm-unit-tests/COPYRIGHT index d35649cb..b9d975fc 100644 --- a/tests/kvm-unit-tests/COPYRIGHT +++ b/tests/kvm-unit-tests/COPYRIGHT @@ -1,4 +1,10 @@ Copyright (C) 2006 Qumranet. +Copyright (C) 2007-2017 by various contributors (see source files for details) -The files in this directory and its subdirectories are licensed under the -GNU LGPL, version 2. +The kvm-unit-tests are free software; the whole package can be redistributed +and/or modified under the terms of the GNU General Public License version 2 +as published by the Free Software Foundation. + +Many files in this directory and its subdirectories are also licensed under +the less restrictive GNU LGPL, version 2, or other compatible licenses. See +the individual files for details. diff --git a/tests/kvm-unit-tests/MAINTAINERS b/tests/kvm-unit-tests/MAINTAINERS index b86dea73..d07a4cfa 100644 --- a/tests/kvm-unit-tests/MAINTAINERS +++ b/tests/kvm-unit-tests/MAINTAINERS @@ -76,6 +76,13 @@ F: powerpc/* F: lib/powerpc/* F: lib/ppc64/* +S390X +M: Thomas Huth +M: David Hildenbrand +L: kvm@vger.kernel.org +F: s390x/* +F: lib/s390x/* + X86 M: Paolo Bonzini M: Radim Krčmář diff --git a/tests/kvm-unit-tests/Makefile b/tests/kvm-unit-tests/Makefile index 16ce2970..72313347 100644 --- a/tests/kvm-unit-tests/Makefile +++ b/tests/kvm-unit-tests/Makefile @@ -1,5 +1,5 @@ -SHELL := /bin/bash +SHELL := /usr/bin/env bash ifeq ($(wildcard config.mak),) $(error run ./configure first. See ./configure -h) @@ -7,8 +7,12 @@ endif include config.mak +# Set search path for all sources +VPATH = $(SRCDIR) + libdirs-get = $(shell [ -d "lib/$(1)" ] && echo "lib/$(1) lib/$(1)/asm") ARCH_LIBDIRS := $(call libdirs-get,$(ARCH)) $(call libdirs-get,$(TEST_DIR)) +OBJDIRS := $(ARCH_LIBDIRS) DESTDIR := $(PREFIX)/share/kvm-unit-tests/ @@ -30,13 +34,15 @@ cflatobjs := \ # libfdt paths LIBFDT_objdir = lib/libfdt -LIBFDT_srcdir = lib/libfdt +LIBFDT_srcdir = $(SRCDIR)/lib/libfdt LIBFDT_archive = $(LIBFDT_objdir)/libfdt.a LIBFDT_include = $(addprefix $(LIBFDT_srcdir)/,$(LIBFDT_INCLUDES)) LIBFDT_version = $(addprefix $(LIBFDT_srcdir)/,$(LIBFDT_VERSION)) -#include architecure specific make rules -include $(TEST_DIR)/Makefile +OBJDIRS += $(LIBFDT_objdir) + +#include architecture specific make rules +include $(SRCDIR)/$(TEST_DIR)/Makefile # cc-option # Usage: OP_CFLAGS+=$(call cc-option, -falign-functions=0, -malign-functions=0) @@ -44,8 +50,10 @@ include $(TEST_DIR)/Makefile cc-option = $(shell if $(CC) $(1) -S -o /dev/null -xc /dev/null \ > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;) -CFLAGS += -g -CFLAGS += $(autodepend-flags) -Wall -Werror +COMMON_CFLAGS += -g $(autodepend-flags) +COMMON_CFLAGS += -Wall -Wwrite-strings -Wclobbered -Wempty-body -Wuninitialized +COMMON_CFLAGS += -Wignored-qualifiers -Wunused-but-set-parameter +COMMON_CFLAGS += -Werror frame-pointer-flag=-f$(if $(KEEP_FRAME_POINTER),no-,)omit-frame-pointer fomit_frame_pointer := $(call cc-option, $(frame-pointer-flag), "") fnostack_protector := $(call cc-option, -fno-stack-protector, "") @@ -53,34 +61,42 @@ fnostack_protector_all := $(call cc-option, -fno-stack-protector-all, "") wno_frame_address := $(call cc-option, -Wno-frame-address, "") fno_pic := $(call cc-option, -fno-pic, "") no_pie := $(call cc-option, -no-pie, "") -CFLAGS += $(fomit_frame_pointer) -CFLAGS += $(fno_stack_protector) -CFLAGS += $(fno_stack_protector_all) -CFLAGS += $(wno_frame_address) -CFLAGS += $(if $(U32_LONG_FMT),-D__U32_LONG_FMT__,) -CFLAGS += $(fno_pic) $(no_pie) +COMMON_CFLAGS += $(fomit_frame_pointer) +COMMON_CFLAGS += $(fno_stack_protector) +COMMON_CFLAGS += $(fno_stack_protector_all) +COMMON_CFLAGS += $(wno_frame_address) +COMMON_CFLAGS += $(if $(U32_LONG_FMT),-D__U32_LONG_FMT__,) +COMMON_CFLAGS += $(fno_pic) $(no_pie) -CXXFLAGS += $(CFLAGS) +CFLAGS += $(COMMON_CFLAGS) +CFLAGS += -Wmissing-parameter-type -Wold-style-declaration -Woverride-init + +CXXFLAGS += $(COMMON_CFLAGS) autodepend-flags = -MMD -MF $(dir $*).$(notdir $*).d LDFLAGS += $(CFLAGS) -LDFLAGS += -pthread -lrt $(libcflat): $(cflatobjs) $(AR) rcs $@ $^ include $(LIBFDT_srcdir)/Makefile.libfdt -$(LIBFDT_archive): CFLAGS += -ffreestanding -I lib -I lib/libfdt -Wno-sign-compare +$(LIBFDT_archive): CFLAGS += -ffreestanding -I $(SRCDIR)/lib -I $(SRCDIR)/lib/libfdt -Wno-sign-compare $(LIBFDT_archive): $(addprefix $(LIBFDT_objdir)/,$(LIBFDT_OBJS)) $(AR) rcs $@ $^ + +# Build directory target +.PHONY: directories +directories: + @mkdir -p $(OBJDIRS) + %.o: %.S $(CC) $(CFLAGS) -c -nostdlib -o $@ $< -include */.*.d */*/.*.d -all: $(shell git rev-parse --verify --short=8 HEAD >build-head 2>/dev/null) +all: directories $(shell cd $(SRCDIR) && git rev-parse --verify --short=8 HEAD >$(PWD)/build-head 2>/dev/null) standalone: all @scripts/mkstandalone.sh diff --git a/tests/kvm-unit-tests/configure b/tests/kvm-unit-tests/configure index b653b65f..e1efb3ca 100755 --- a/tests/kvm-unit-tests/configure +++ b/tests/kvm-unit-tests/configure @@ -1,17 +1,20 @@ -#!/bin/bash +#!/usr/bin/env bash +srcdir=$(cd "$(dirname "$0")"; pwd) prefix=/usr/local cc=gcc +cxx=g++ ld=ld objcopy=objcopy objdump=objdump ar=ar addr2line=addr2line -arch=i386 +arch=`uname -m | sed -e 's/i.86/i386/;s/arm.*/arm/;s/ppc64.*/ppc64/'` host=$arch cross_prefix= endian="" pretty_print_stacks=yes +environ_default=yes u32_long= usage() { @@ -23,11 +26,15 @@ usage() { --processor=PROCESSOR processor to compile for ($arch) --cross-prefix=PREFIX cross compiler prefix --cc=CC c compiler to use ($cc) + --cxx=CXX c++ compiler to use ($cxx) --ld=LD ld linker to use ($ld) --prefix=PREFIX where to install things ($prefix) --endian=ENDIAN endianness to compile for (little or big, ppc64 only) --[enable|disable]-pretty-print-stacks enable or disable pretty stack printing (enabled by default) + --[enable|disable]-default-environ + enable or disable the generation of a default environ when + no environ is provided by the user (enabled by default) EOF exit 1 } @@ -58,6 +65,9 @@ while [[ "$1" = -* ]]; do --cc) cc="$arg" ;; + --cxx) + cxx="$arg" + ;; --ld) ld="$arg" ;; @@ -67,6 +77,12 @@ while [[ "$1" = -* ]]; do --disable-pretty-print-stacks) pretty_print_stacks=no ;; + --enable-default-environ) + environ_default=yes + ;; + --disable-default-environ) + environ_default=no + ;; --help) usage ;; @@ -102,12 +118,12 @@ elif [ "$arch" = "ppc64" ]; then else testdir=$arch fi -if [ ! -d $testdir ]; then +if [ ! -d "$srcdir/$testdir" ]; then echo "$testdir does not exist!" exit 1 fi -if [ -f $testdir/run ]; then - ln -fs $testdir/run $testdir-run +if [ -f "$srcdir/$testdir/run" ]; then + ln -fs "$srcdir/$testdir/run" $testdir-run fi # check if uint32_t needs a long format modifier @@ -117,42 +133,56 @@ EOF u32_long=$($cross_prefix$cc -E lib-test.c | grep -v '^#' | grep -q long && echo yes) rm -f lib-test.c -# check for dependent 32 bit libraries -if [ "$arch" != "arm" ]; then -cat << EOF > lib_test.c -#include -#include -#include - -int main () -{} -EOF -$cc -m32 -o /dev/null lib_test.c &> /dev/null -exit=$? -if [ $exit -eq 0 ]; then - api=true +# api/: check for dependent 32 bit libraries and gnu++11 support +if [ "$testdir" = "x86" ]; then + echo 'int main () {}' > lib-test.c + $cc -m32 -o /dev/null -lstdc++ -lpthread -lrt lib-test.c &> /dev/null + exit=$? + $cxx -m32 -o /dev/null -std=gnu++11 lib-test.c &> /dev/null + if [ $? -eq 0 -a $exit -eq 0 ]; then + api=true + fi + rm -f lib-test.c fi -rm -f lib_test.c + +# Are we in a separate build tree? If so, link the Makefile +# and shared stuff so that 'make' and run_tests.sh work. +if test ! -e Makefile; then + echo "linking Makefile..." + ln -s "$srcdir/Makefile" . + + echo "linking tests..." + mkdir -p $testdir + ln -sf "$srcdir/$testdir/run" $testdir/ + ln -sf "$srcdir/$testdir/unittests.cfg" $testdir/ + ln -sf "$srcdir/run_tests.sh" + + echo "linking scripts..." + ln -sf "$srcdir/scripts" fi # link lib/asm for the architecture rm -f lib/asm asm=asm-generic -if [ -d lib/$arch/asm ]; then - asm=$arch/asm -elif [ -d lib/$testdir/asm ]; then - asm=$testdir/asm +if [ -d "$srcdir/lib/$arch/asm" ]; then + asm="$srcdir/lib/$arch/asm" +elif [ -d "$srcdir/lib/$testdir/asm" ]; then + asm="$srcdir/lib/$testdir/asm" fi -ln -s $asm lib/asm +mkdir -p lib +ln -sf "$asm" lib/asm + # create the config cat < config.mak +SRCDIR=$srcdir PREFIX=$prefix HOST=$host ARCH=$arch ARCH_NAME=$arch_name PROCESSOR=$processor CC=$cross_prefix$cc +CXX=$cross_prefix$cxx LD=$cross_prefix$ld OBJCOPY=$cross_prefix$objcopy OBJDUMP=$cross_prefix$objdump @@ -163,5 +193,7 @@ TEST_DIR=$testdir FIRMWARE=$firmware ENDIAN=$endian PRETTY_PRINT_STACKS=$pretty_print_stacks +ENVIRON_DEFAULT=$environ_default +ERRATATXT=errata.txt U32_LONG_FMT=$u32_long EOF diff --git a/tests/kvm-unit-tests/lib/alloc.c b/tests/kvm-unit-tests/lib/alloc.c index 58af52b3..d553a7ec 100644 --- a/tests/kvm-unit-tests/lib/alloc.c +++ b/tests/kvm-unit-tests/lib/alloc.c @@ -25,7 +25,7 @@ void phys_alloc_show(void) int i; spin_lock(&lock); - printf("phys_alloc minimum alignment: 0x%" PRIx64 "\n", + printf("phys_alloc minimum alignment: %#" PRIx64 "\n", (u64)align_min); for (i = 0; i < nr_regions; ++i) printf("%016" PRIx64 "-%016" PRIx64 " [%s]\n", @@ -75,10 +75,10 @@ static phys_addr_t phys_alloc_aligned_safe(phys_addr_t size, size += addr - base; if ((top_safe - base) < size) { - printf("phys_alloc: requested=0x%" PRIx64 - " (align=0x%" PRIx64 "), " - "need=0x%" PRIx64 ", but free=0x%" PRIx64 ". " - "top=0x%" PRIx64 ", top_safe=0x%" PRIx64 "\n", + printf("phys_alloc: requested=%#" PRIx64 + " (align=%#" PRIx64 "), " + "need=%#" PRIx64 ", but free=%#" PRIx64 ". " + "top=%#" PRIx64 ", top_safe=%#" PRIx64 "\n", (u64)size_orig, (u64)align, (u64)size, top_safe - base, (u64)top, top_safe); spin_unlock(&lock); diff --git a/tests/kvm-unit-tests/lib/argv.c b/tests/kvm-unit-tests/lib/argv.c index a37fc879..f0e183a8 100644 --- a/tests/kvm-unit-tests/lib/argv.c +++ b/tests/kvm-unit-tests/lib/argv.c @@ -1,8 +1,16 @@ +/* + * Set up arguments for main() and prepare environment variables + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ + #include "libcflat.h" +#include "argv.h" #include "auxinfo.h" int __argc; -char *__args; +const char *__args; char *__argv[100]; char *__environ[200]; @@ -15,7 +23,7 @@ static char *copy_ptr = args_copy; #define isalpha(c) (((c) >= 'A' && (c) <= 'Z') || ((c) >= 'a' && (c) <= 'z') || (c) == '_') #define isalnum(c) (isalpha(c) || ((c) >= '0' && (c) <= '9')) -static char *skip_blanks(char *p) +static const char *skip_blanks(const char *p) { while (isblank(*p)) ++p; @@ -24,7 +32,7 @@ static char *skip_blanks(char *p) void __setup_args(void) { - char *args = __args; + const char *args = __args; char **argv = __argv + __argc; while (*(args = skip_blanks(args)) != '\0') { @@ -36,7 +44,7 @@ void __setup_args(void) __argc = argv - __argv; } -void setup_args(char *args) +static void setup_args(const char *args) { if (!args) return; @@ -45,16 +53,13 @@ void setup_args(char *args) __setup_args(); } -void setup_args_progname(char *args) +void setup_args_progname(const char *args) { __argv[0] = copy_ptr; strcpy(__argv[0], auxinfo.progname); copy_ptr += strlen(auxinfo.progname) + 1; ++__argc; - if (args) { - __args = args; - __setup_args(); - } + setup_args(args); } static char *env_eol(char *env) diff --git a/tests/kvm-unit-tests/lib/asm-generic/io.h b/tests/kvm-unit-tests/lib/asm-generic/io.h index 91a2d799..88972f3b 100644 --- a/tests/kvm-unit-tests/lib/asm-generic/io.h +++ b/tests/kvm-unit-tests/lib/asm-generic/io.h @@ -5,9 +5,9 @@ * adapted from the Linux kernel's include/asm-generic/io.h * and arch/arm/include/asm/io.h * - * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #include "libcflat.h" #include "asm/page.h" diff --git a/tests/kvm-unit-tests/lib/asm-generic/page.h b/tests/kvm-unit-tests/lib/asm-generic/page.h index 7b8a08bf..5ed08612 100644 --- a/tests/kvm-unit-tests/lib/asm-generic/page.h +++ b/tests/kvm-unit-tests/lib/asm-generic/page.h @@ -4,9 +4,9 @@ * asm-generic/page.h * adapted from the Linux kernel's include/asm-generic/page.h * - * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #include diff --git a/tests/kvm-unit-tests/lib/asm-generic/spinlock.h b/tests/kvm-unit-tests/lib/asm-generic/spinlock.h index 31417442..31fa510e 100644 --- a/tests/kvm-unit-tests/lib/asm-generic/spinlock.h +++ b/tests/kvm-unit-tests/lib/asm-generic/spinlock.h @@ -1,4 +1,18 @@ #ifndef _ASM_GENERIC_SPINLOCK_H_ #define _ASM_GENERIC_SPINLOCK_H_ -#error need architecture specific asm/spinlock.h + +struct spinlock { + unsigned int v; +}; + +static inline void spin_lock(struct spinlock *lock) +{ + while (__sync_lock_test_and_set(&lock->v, 1)); +} + +static inline void spin_unlock(struct spinlock *lock) +{ + __sync_lock_release(&lock->v); +} + #endif diff --git a/tests/kvm-unit-tests/lib/auxinfo.h b/tests/kvm-unit-tests/lib/auxinfo.h index ef2376b4..669ba5da 100644 --- a/tests/kvm-unit-tests/lib/auxinfo.h +++ b/tests/kvm-unit-tests/lib/auxinfo.h @@ -1,3 +1,7 @@ +/* + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ #ifndef _AUXINFO_H_ #define _AUXINFO_H_ struct auxinfo { diff --git a/tests/kvm-unit-tests/lib/bitops.h b/tests/kvm-unit-tests/lib/bitops.h index 9aa847e1..185c5d36 100644 --- a/tests/kvm-unit-tests/lib/bitops.h +++ b/tests/kvm-unit-tests/lib/bitops.h @@ -2,12 +2,12 @@ #define _BITOPS_H_ /* - * Adapated from + * Adapted from * include/linux/bitops.h * - * Copyright (C) 2015, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #define BITS_PER_LONG_LONG 64 diff --git a/tests/kvm-unit-tests/lib/errata.h b/tests/kvm-unit-tests/lib/errata.h index 5e63f73b..f3ebca2d 100644 --- a/tests/kvm-unit-tests/lib/errata.h +++ b/tests/kvm-unit-tests/lib/errata.h @@ -1,3 +1,9 @@ +/* + * errata functions + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ #ifndef _ERRATA_H_ #define _ERRATA_H_ @@ -7,16 +13,33 @@ #define _ERRATA_RELAXED(erratum) errata_relaxed("ERRATA_" # erratum) #define ERRATA_RELAXED(erratum) _ERRATA_RELAXED(erratum) +static inline bool errata_force(void) +{ + char *s = getenv("ERRATA_FORCE"); + + return s && (*s == '1' || *s == 'y' || *s == 'Y'); +} + static inline bool errata(const char *erratum) { - char *s = getenv(erratum); + char *s; + + if (errata_force()) + return true; + + s = getenv(erratum); return s && (*s == '1' || *s == 'y' || *s == 'Y'); } static inline bool errata_relaxed(const char *erratum) { - char *s = getenv(erratum); + char *s; + + if (errata_force()) + return true; + + s = getenv(erratum); return !(s && (*s == '0' || *s == 'n' || *s == 'N')); } diff --git a/tests/kvm-unit-tests/lib/libcflat.h b/tests/kvm-unit-tests/lib/libcflat.h index 96a37926..a5c42903 100644 --- a/tests/kvm-unit-tests/lib/libcflat.h +++ b/tests/kvm-unit-tests/lib/libcflat.h @@ -96,15 +96,25 @@ extern int vsnprintf(char *buf, int size, const char *fmt, va_list va) extern int vprintf(const char *fmt, va_list va) __attribute__((format(printf, 1, 0))); +void report_prefix_pushf(const char *prefix_fmt, ...) + __attribute__((format(printf, 1, 2))); extern void report_prefix_push(const char *prefix); extern void report_prefix_pop(void); -extern void report(const char *msg_fmt, bool pass, ...); -extern void report_xfail(const char *msg_fmt, bool xfail, bool pass, ...); -extern void report_abort(const char *msg_fmt, ...); -extern void report_skip(const char *msg_fmt, ...); -extern void report_info(const char *msg_fmt, ...); +extern void report(const char *msg_fmt, bool pass, ...) + __attribute__((format(printf, 1, 3))); +extern void report_xfail(const char *msg_fmt, bool xfail, bool pass, ...) + __attribute__((format(printf, 1, 4))); +extern void report_abort(const char *msg_fmt, ...) + __attribute__((format(printf, 1, 2))); +extern void report_skip(const char *msg_fmt, ...) + __attribute__((format(printf, 1, 2))); +extern void report_info(const char *msg_fmt, ...) + __attribute__((format(printf, 1, 2))); +extern void report_pass(void); extern int report_summary(void); +bool simple_glob(const char *text, const char *pattern); + extern void dump_stack(void); extern void dump_frame_stack(const void *instruction, const void *frame); @@ -124,9 +134,26 @@ do { \ } \ } while (0) +#define assert_msg(cond, fmt, args...) \ +do { \ + if (!(cond)) { \ + printf("%s:%d: assert failed: %s: " fmt "\n", \ + __FILE__, __LINE__, #cond, ## args); \ + dump_stack(); \ + abort(); \ + } \ +} while (0) + static inline bool is_power_of_2(unsigned long n) { return n && !(n & (n - 1)); } +/* + * One byte per bit, a ' between each group of 4 bits, and a null terminator. + */ +#define BINSTR_SZ (sizeof(unsigned long) * 8 + sizeof(unsigned long) * 2) +void binstr(unsigned long x, char out[BINSTR_SZ]); +void print_binstr(unsigned long x); + #endif diff --git a/tests/kvm-unit-tests/lib/pci-edu.c b/tests/kvm-unit-tests/lib/pci-edu.c index f94962f0..0e031054 100644 --- a/tests/kvm-unit-tests/lib/pci-edu.c +++ b/tests/kvm-unit-tests/lib/pci-edu.c @@ -48,7 +48,7 @@ void edu_dma(struct pci_edu_dev *dev, iova_t iova, assert(size <= EDU_DMA_SIZE_MAX); assert(dev_offset < EDU_DMA_SIZE_MAX); - printf("edu device DMA start %s addr 0x%" PRIx64 " size 0x%lu off 0x%x\n", + printf("edu device DMA start %s addr %#" PRIx64 " size %lu off %#x\n", from_device ? "FROM" : "TO", iova, (ulong)size, dev_offset); diff --git a/tests/kvm-unit-tests/lib/pci-testdev.c b/tests/kvm-unit-tests/lib/pci-testdev.c index 7d298e69..039bb447 100644 --- a/tests/kvm-unit-tests/lib/pci-testdev.c +++ b/tests/kvm-unit-tests/lib/pci-testdev.c @@ -131,8 +131,8 @@ static bool pci_testdev_one(struct pci_test_dev_hdr *test, return (int)count == nr_writes; } -void pci_testdev_print(struct pci_test_dev_hdr *test, - struct pci_testdev_ops *ops) +static void pci_testdev_print(struct pci_test_dev_hdr *test, + struct pci_testdev_ops *ops) { bool io = (ops == &pci_testdev_io_ops); int i; diff --git a/tests/kvm-unit-tests/lib/pci.c b/tests/kvm-unit-tests/lib/pci.c index daf39810..daa33e1f 100644 --- a/tests/kvm-unit-tests/lib/pci.c +++ b/tests/kvm-unit-tests/lib/pci.c @@ -49,7 +49,7 @@ bool pci_setup_msi(struct pci_dev *dev, uint64_t msi_addr, uint32_t msi_data) assert(dev); if (!dev->msi_offset) { - printf("MSI: dev 0x%x does not support MSI.\n", dev->bdf); + printf("MSI: dev %#x does not support MSI.\n", dev->bdf); return false; } @@ -112,6 +112,8 @@ uint32_t pci_bar_mask(uint32_t bar) uint32_t pci_bar_get(struct pci_dev *dev, int bar_num) { + ASSERT_BAR_NUM(bar_num); + return pci_config_readl(dev->bdf, PCI_BASE_ADDRESS_0 + bar_num * 4); } @@ -134,6 +136,8 @@ static phys_addr_t __pci_bar_get_addr(struct pci_dev *dev, int bar_num) phys_addr_t pci_bar_get_addr(struct pci_dev *dev, int bar_num) { + ASSERT_BAR_NUM(bar_num); + return dev->resource[bar_num]; } @@ -141,11 +145,19 @@ void pci_bar_set_addr(struct pci_dev *dev, int bar_num, phys_addr_t addr) { int off = PCI_BASE_ADDRESS_0 + bar_num * 4; + assert(addr != INVALID_PHYS_ADDR); + assert(dev->resource[bar_num] != INVALID_PHYS_ADDR); + + ASSERT_BAR_NUM(bar_num); + if (pci_bar_is64(dev, bar_num)) + ASSERT_BAR_NUM(bar_num + 1); + else + assert((addr >> 32) == 0); + pci_config_writel(dev->bdf, off, (uint32_t)addr); dev->resource[bar_num] = addr; if (pci_bar_is64(dev, bar_num)) { - assert(bar_num + 1 < PCI_BAR_NUM); pci_config_writel(dev->bdf, off + 4, (uint32_t)(addr >> 32)); dev->resource[bar_num + 1] = dev->resource[bar_num]; } @@ -283,10 +295,10 @@ static void pci_cap_print(struct pci_dev *dev, int cap_offset, int cap_id) break; } default: - printf("\tcapability 0x%02x ", cap_id); + printf("\tcapability %#04x ", cap_id); break; } - printf("at offset 0x%02x\n", cap_offset); + printf("at offset %#04x\n", cap_offset); } void pci_dev_print(struct pci_dev *dev) diff --git a/tests/kvm-unit-tests/lib/pci.h b/tests/kvm-unit-tests/lib/pci.h index 03cc0a72..689f03ca 100644 --- a/tests/kvm-unit-tests/lib/pci.h +++ b/tests/kvm-unit-tests/lib/pci.h @@ -18,6 +18,9 @@ enum { #define PCI_BAR_NUM 6 #define PCI_DEVFN_MAX 256 +#define ASSERT_BAR_NUM(bar_num) \ + do { assert(bar_num >= 0 && bar_num < PCI_BAR_NUM); } while (0) + #define PCI_BDF_GET_DEVFN(x) ((x) & 0xff) #define PCI_BDF_GET_BUS(x) (((x) >> 8) & 0xff) diff --git a/tests/kvm-unit-tests/lib/printf.c b/tests/kvm-unit-tests/lib/printf.c index 2aec59aa..1269723e 100644 --- a/tests/kvm-unit-tests/lib/printf.c +++ b/tests/kvm-unit-tests/lib/printf.c @@ -1,3 +1,10 @@ +/* + * libc printf and friends + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ + #include "libcflat.h" #define BUFSZ 2000 @@ -11,6 +18,7 @@ typedef struct pstream { typedef struct strprops { char pad; int npad; + bool alternate; } strprops_t; static void addchar(pstream_t *p, char c) @@ -22,7 +30,7 @@ static void addchar(pstream_t *p, char c) ++p->added; } -void print_str(pstream_t *p, const char *s, strprops_t props) +static void print_str(pstream_t *p, const char *s, strprops_t props) { const char *s_orig = s; int npad = props.npad; @@ -50,7 +58,7 @@ void print_str(pstream_t *p, const char *s, strprops_t props) static char digits[16] = "0123456789abcdef"; -void print_int(pstream_t *ps, long long n, int base, strprops_t props) +static void print_int(pstream_t *ps, long long n, int base, strprops_t props) { char buf[sizeof(long) * 3 + 2], *p = buf; int s = 0, i; @@ -84,10 +92,10 @@ void print_int(pstream_t *ps, long long n, int base, strprops_t props) print_str(ps, buf, props); } -void print_unsigned(pstream_t *ps, unsigned long long n, int base, - strprops_t props) +static void print_unsigned(pstream_t *ps, unsigned long long n, int base, + strprops_t props) { - char buf[sizeof(long) * 3 + 1], *p = buf; + char buf[sizeof(long) * 3 + 3], *p = buf; int i; while (n) { @@ -97,6 +105,18 @@ void print_unsigned(pstream_t *ps, unsigned long long n, int base, if (p == buf) *p++ = '0'; + else if (props.alternate && base == 16) { + if (props.pad == '0') { + addchar(ps, '0'); + addchar(ps, 'x'); + + if (props.npad > 0) + props.npad = MAX(props.npad - 2, 0); + } else { + *p++ = 'x'; + *p++ = '0'; + } + } for (i = 0; i < (p - buf) / 2; ++i) { char tmp; @@ -157,6 +177,9 @@ int vsnprintf(char *buf, int size, const char *fmt, va_list va) case '\0': --fmt; break; + case '#': + props.alternate = true; + goto morefmt; case '0': props.pad = '0'; ++fmt; @@ -169,6 +192,15 @@ int vsnprintf(char *buf, int size, const char *fmt, va_list va) case 'l': ++nlong; goto morefmt; + case 't': + case 'z': + /* Here we only care that sizeof(size_t) == sizeof(long). + * On a 32-bit platform it doesn't matter that size_t is + * typedef'ed to int or long; va_arg will work either way. + * Same for ptrdiff_t (%td). + */ + nlong = 1; + goto morefmt; case 'd': switch (nlong) { case 0: @@ -209,7 +241,7 @@ int vsnprintf(char *buf, int size, const char *fmt, va_list va) } break; case 'p': - print_str(&s, "0x", props); + props.alternate = true; print_unsigned(&s, (unsigned long)va_arg(va, void *), 16, props); break; case 's': @@ -221,7 +253,6 @@ int vsnprintf(char *buf, int size, const char *fmt, va_list va) } } *s.buffer = 0; - ++s.added; return s.added; } @@ -259,3 +290,33 @@ int printf(const char *fmt, ...) puts(buf); return r; } + +void binstr(unsigned long x, char out[BINSTR_SZ]) +{ + int i; + char *c; + int n; + + n = sizeof(unsigned long) * 8; + i = 0; + c = &out[0]; + for (;;) { + *c++ = (x & (1ul << (n - i - 1))) ? '1' : '0'; + i++; + + if (i == n) { + *c = '\0'; + break; + } + if (i % 4 == 0) + *c++ = '\''; + } + assert(c + 1 - &out[0] == BINSTR_SZ); +} + +void print_binstr(unsigned long x) +{ + char out[BINSTR_SZ]; + binstr(x, out); + printf("%s", out); +} diff --git a/tests/kvm-unit-tests/lib/report.c b/tests/kvm-unit-tests/lib/report.c index e24e8138..5da27ab3 100644 --- a/tests/kvm-unit-tests/lib/report.c +++ b/tests/kvm-unit-tests/lib/report.c @@ -17,14 +17,49 @@ static unsigned int tests, failures, xfailures, skipped; static char prefixes[256]; static struct spinlock lock; -void report_prefix_push(const char *prefix) +#define PREFIX_DELIMITER ": " + +void report_pass(void) { spin_lock(&lock); - strcat(prefixes, prefix); - strcat(prefixes, ": "); + tests++; spin_unlock(&lock); } +void report_prefix_pushf(const char *prefix_fmt, ...) +{ + va_list va; + unsigned int len; + int start; + + spin_lock(&lock); + + len = strlen(prefixes); + assert_msg(len < sizeof(prefixes), "%d >= %zu", len, sizeof(prefixes)); + start = len; + + va_start(va, prefix_fmt); + len += vsnprintf(&prefixes[len], sizeof(prefixes) - len, prefix_fmt, + va); + va_end(va); + assert_msg(len < sizeof(prefixes), "%d >= %zu", len, sizeof(prefixes)); + + assert_msg(!strstr(&prefixes[start], PREFIX_DELIMITER), + "Prefix \"%s\" contains delimiter \"" PREFIX_DELIMITER "\"", + &prefixes[start]); + + len += snprintf(&prefixes[len], sizeof(prefixes) - len, + PREFIX_DELIMITER); + assert_msg(len < sizeof(prefixes), "%d >= %zu", len, sizeof(prefixes)); + + spin_unlock(&lock); +} + +void report_prefix_push(const char *prefix) +{ + report_prefix_pushf("%s", prefix); +} + void report_prefix_pop(void) { char *p, *q; @@ -34,9 +69,9 @@ void report_prefix_pop(void) if (!*prefixes) return; - for (p = prefixes, q = strstr(p, ": ") + 2; + for (p = prefixes, q = strstr(p, PREFIX_DELIMITER) + 2; *q; - p = q, q = strstr(p, ": ") + 2) + p = q, q = strstr(p, PREFIX_DELIMITER) + 2) ; *p = '\0'; @@ -46,9 +81,9 @@ void report_prefix_pop(void) static void va_report(const char *msg_fmt, bool pass, bool xfail, bool skip, va_list va) { - char *prefix = skip ? "SKIP" - : xfail ? (pass ? "XPASS" : "XFAIL") - : (pass ? "PASS" : "FAIL"); + const char *prefix = skip ? "SKIP" + : xfail ? (pass ? "XPASS" : "XFAIL") + : (pass ? "PASS" : "FAIL"); spin_lock(&lock); diff --git a/tests/kvm-unit-tests/lib/setjmp.h b/tests/kvm-unit-tests/lib/setjmp.h index 334f466f..2c56b4c6 100644 --- a/tests/kvm-unit-tests/lib/setjmp.h +++ b/tests/kvm-unit-tests/lib/setjmp.h @@ -1,3 +1,9 @@ +/* + * setjmp/longjmp prototypes + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ #ifndef LIBCFLAT_SETJMP_H #define LIBCFLAT_SETJMP_H 1 diff --git a/tests/kvm-unit-tests/lib/stack.c b/tests/kvm-unit-tests/lib/stack.c index b0a02950..bdb23fde 100644 --- a/tests/kvm-unit-tests/lib/stack.c +++ b/tests/kvm-unit-tests/lib/stack.c @@ -1,3 +1,10 @@ +/* + * stack related functions + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ + #include #include diff --git a/tests/kvm-unit-tests/lib/stack.h b/tests/kvm-unit-tests/lib/stack.h index cfc66f44..10fc2f79 100644 --- a/tests/kvm-unit-tests/lib/stack.h +++ b/tests/kvm-unit-tests/lib/stack.h @@ -1,3 +1,9 @@ +/* + * Header for stack related functions + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ #ifndef _STACK_H_ #define _STACK_H_ diff --git a/tests/kvm-unit-tests/lib/string.c b/tests/kvm-unit-tests/lib/string.c index 833f22be..018dcc87 100644 --- a/tests/kvm-unit-tests/lib/string.c +++ b/tests/kvm-unit-tests/lib/string.c @@ -1,3 +1,10 @@ +/* + * libc string functions + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ + #include "libcflat.h" unsigned long strlen(const char *buf) @@ -173,3 +180,44 @@ char *getenv(const char *name) } return NULL; } + +/* Very simple glob matching. Allows '*' at beginning and end of pattern. */ +bool simple_glob(const char *text, const char *pattern) +{ + bool star_start = false; + bool star_end = false; + size_t n = strlen(pattern); + char copy[n + 1]; + + if (pattern[0] == '*') { + pattern += 1; + n -= 1; + star_start = true; + } + + strcpy(copy, pattern); + + if (n > 0 && pattern[n - 1] == '*') { + n -= 1; + copy[n] = '\0'; + star_end = true; + } + + if (star_start && star_end) + return strstr(text, copy); + + if (star_end) + return strstr(text, copy) == text; + + if (star_start) { + size_t text_len = strlen(text); + const char *suffix; + + if (n > text_len) + return false; + suffix = text + text_len - n; + return !strcmp(suffix, copy); + } + + return !strcmp(text, copy); +} diff --git a/tests/kvm-unit-tests/lib/string.h b/tests/kvm-unit-tests/lib/string.h index 2391013a..493d51ba 100644 --- a/tests/kvm-unit-tests/lib/string.h +++ b/tests/kvm-unit-tests/lib/string.h @@ -1,3 +1,9 @@ +/* + * Header for libc string functions + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License version 2. + */ #ifndef __STRING_H #define __STRING_H diff --git a/tests/kvm-unit-tests/lib/util.c b/tests/kvm-unit-tests/lib/util.c index 69b18100..a9055413 100644 --- a/tests/kvm-unit-tests/lib/util.c +++ b/tests/kvm-unit-tests/lib/util.c @@ -4,6 +4,7 @@ * This work is licensed under the terms of the GNU LGPL, version 2. */ #include +#include "util.h" int parse_keyval(char *s, long *val) { diff --git a/tests/kvm-unit-tests/lib/virtio-mmio.c b/tests/kvm-unit-tests/lib/virtio-mmio.c index fa8dd5b8..e4a92f19 100644 --- a/tests/kvm-unit-tests/lib/virtio-mmio.c +++ b/tests/kvm-unit-tests/lib/virtio-mmio.c @@ -1,9 +1,9 @@ /* * virtqueue support adapted from the Linux kernel. * - * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #include "libcflat.h" #include "devicetree.h" diff --git a/tests/kvm-unit-tests/lib/virtio-mmio.h b/tests/kvm-unit-tests/lib/virtio-mmio.h index 8046a474..250f28a0 100644 --- a/tests/kvm-unit-tests/lib/virtio-mmio.h +++ b/tests/kvm-unit-tests/lib/virtio-mmio.h @@ -3,9 +3,9 @@ /* * A minimal implementation of virtio-mmio. Adapted from the Linux Kernel. * - * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #include "libcflat.h" #include "asm/page.h" diff --git a/tests/kvm-unit-tests/lib/virtio.c b/tests/kvm-unit-tests/lib/virtio.c index 9532d1ae..69054757 100644 --- a/tests/kvm-unit-tests/lib/virtio.c +++ b/tests/kvm-unit-tests/lib/virtio.c @@ -1,9 +1,9 @@ /* * virtqueue support adapted from the Linux kernel. * - * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #include "libcflat.h" #include "asm/io.h" diff --git a/tests/kvm-unit-tests/lib/virtio.h b/tests/kvm-unit-tests/lib/virtio.h index 4801e204..2c31fdc7 100644 --- a/tests/kvm-unit-tests/lib/virtio.h +++ b/tests/kvm-unit-tests/lib/virtio.h @@ -4,9 +4,9 @@ * A minimal implementation of virtio. * Structures adapted from the Linux Kernel. * - * Copyright (C) 2014, Red Hat Inc, Andrew Jones + * Copyright (C) 2017, Red Hat Inc, Andrew Jones * - * This work is licensed under the terms of the GNU LGPL, version 2. + * This work is licensed under the terms of the GNU GPL, version 2. */ #include "libcflat.h" diff --git a/tests/kvm-unit-tests/lib/x86/asm/page.h b/tests/kvm-unit-tests/lib/x86/asm/page.h index c43bab28..562594df 100644 --- a/tests/kvm-unit-tests/lib/x86/asm/page.h +++ b/tests/kvm-unit-tests/lib/x86/asm/page.h @@ -31,6 +31,8 @@ #define PT64_NX_MASK (1ull << 63) #define PT_ADDR_MASK GENMASK_ULL(51, 12) +#define PT_AD_MASK (PT_ACCESSED_MASK | PT_DIRTY_MASK) + #ifdef __x86_64__ #define PAGE_LEVEL 4 #define PGDIR_WIDTH 9 diff --git a/tests/kvm-unit-tests/lib/x86/asm/spinlock.h b/tests/kvm-unit-tests/lib/x86/asm/spinlock.h index 4b0cb331..692020c5 100644 --- a/tests/kvm-unit-tests/lib/x86/asm/spinlock.h +++ b/tests/kvm-unit-tests/lib/x86/asm/spinlock.h @@ -1,11 +1,6 @@ #ifndef __ASM_SPINLOCK_H #define __ASM_SPINLOCK_H -struct spinlock { - int v; -}; - -void spin_lock(struct spinlock *lock); -void spin_unlock(struct spinlock *lock); +#include #endif diff --git a/tests/kvm-unit-tests/lib/x86/desc.c b/tests/kvm-unit-tests/lib/x86/desc.c index 402204dd..a5272916 100644 --- a/tests/kvm-unit-tests/lib/x86/desc.c +++ b/tests/kvm-unit-tests/lib/x86/desc.c @@ -69,7 +69,7 @@ static void unhandled_exception(struct ex_regs *regs, bool cpu) cpu ? "cpu " : "", regs->vector, exception_mnemonic(regs->vector), regs->rip); if (regs->vector == 14) - printf("PF at 0x%lx addr 0x%lx\n", regs->rip, read_cr2()); + printf("PF at %#lx addr %#lx\n", regs->rip, read_cr2()); printf("error_code=%04lx rflags=%08lx cs=%08lx\n" "rax=%016lx rcx=%016lx rdx=%016lx rbx=%016lx\n" @@ -117,13 +117,16 @@ static void check_exception_table(struct ex_regs *regs) unhandled_exception(regs, false); } -static void (*exception_handlers[32])(struct ex_regs *regs); +static handler exception_handlers[32]; - -void handle_exception(u8 v, void (*func)(struct ex_regs *regs)) +handler handle_exception(u8 v, handler fn) { + handler old; + + old = exception_handlers[v]; if (v < 32) - exception_handlers[v] = func; + exception_handlers[v] = fn; + return old; } #ifndef __x86_64__ @@ -262,19 +265,6 @@ bool exception_rflags_rf(void) static char intr_alt_stack[4096]; #ifndef __x86_64__ -/* - * GDT, with 6 entries: - * 0x00 - NULL descriptor - * 0x08 - Code segment (ring 0) - * 0x10 - Data segment (ring 0) - * 0x18 - Not present code segment (ring 0) - * 0x20 - Code segment (ring 3) - * 0x28 - Data segment (ring 3) - * 0x30 - Interrupt task - * 0x38 to 0x78 - Free to use for test cases - * 0x80 - Primary task (CPU 0) - */ - void set_gdt_entry(int sel, u32 base, u32 limit, u8 access, u8 gran) { int num = sel >> 3; @@ -385,19 +375,21 @@ static void exception_handler(struct ex_regs *regs) /* longjmp must happen after iret, so do not do it now. */ exception = true; regs->rip = (unsigned long)&exception_handler_longjmp; + regs->cs = read_cs(); } bool test_for_exception(unsigned int ex, void (*trigger_func)(void *data), void *data) { + handler old; jmp_buf jmpbuf; int ret; - handle_exception(ex, exception_handler); + old = handle_exception(ex, exception_handler); ret = set_exception_jmpbuf(jmpbuf); if (ret == 0) trigger_func(data); - handle_exception(ex, NULL); + handle_exception(ex, old); return ret; } diff --git a/tests/kvm-unit-tests/lib/x86/desc.h b/tests/kvm-unit-tests/lib/x86/desc.h index be52fd4e..3bf8fbe5 100644 --- a/tests/kvm-unit-tests/lib/x86/desc.h +++ b/tests/kvm-unit-tests/lib/x86/desc.h @@ -20,6 +20,8 @@ struct ex_regs { unsigned long rflags; }; +typedef void (*handler)(struct ex_regs *regs); + typedef struct { u16 prev; u16 res1; @@ -85,24 +87,59 @@ typedef struct __attribute__((packed)) { #define UD_VECTOR 6 #define GP_VECTOR 13 -#define KERNEL_CS 0x08 -#define KERNEL_DS 0x10 -#define NP_SEL 0x18 -#define USER_CS 0x23 -#define USER_DS 0x2b +/* + * selector 32-bit 64-bit + * 0x00 NULL descriptor NULL descriptor + * 0x08 ring-0 code segment (32-bit) ring-0 code segment (64-bit) + * 0x10 ring-0 data segment (32-bit) ring-0 data segment (32/64-bit) + * 0x18 ring-0 code segment (P=0) ring-0 code segment (64-bit, P=0) + * 0x20 intr_alt_stack TSS ring-0 code segment (32-bit) + * 0x28 ring-0 code segment (16-bit) same + * 0x30 ring-0 data segment (16-bit) same + * 0x38 (0x3b) ring-3 code segment (32-bit) same + * 0x40 (0x43) ring-3 data segment (32-bit) ring-3 data segment (32/64-bit) + * 0x48 (0x4b) **unused** ring-3 code segment (64-bit) + * 0x50--0x78 free to use for test cases same + * 0x80 primary TSS (CPU 0) same + * + * Note that the same segment can be used for 32-bit and 64-bit data segments + * (the L bit is only defined for code segments) + * + * Selectors 0x08-0x10 and 0x3b-0x4b are set up for use with the SYSCALL + * and SYSRET instructions. + */ + +#define KERNEL_CS 0x08 +#define KERNEL_DS 0x10 +#define NP_SEL 0x18 +#ifdef __x86_64__ +#define KERNEL_CS32 0x20 +#else +#define TSS_INTR 0x20 +#endif +#define KERNEL_CS16 0x28 +#define KERNEL_DS16 0x30 +#define USER_CS32 0x3b +#define USER_DS 0x43 +#ifdef __x86_64__ +#define USER_CS64 0x4b +#endif + +/* Synonyms */ +#define KERNEL_DS32 KERNEL_DS +#define USER_DS32 USER_DS + #ifdef __x86_64__ #define KERNEL_CS64 KERNEL_CS +#define USER_CS USER_CS64 #define KERNEL_DS64 KERNEL_DS -#define KERNEL_CS32 0x30 -#define KERNEL_DS32 0x38 -#define KERNEL_CS16 0x40 -#define KERNEL_DS16 0x48 +#define USER_DS64 USER_DS #else #define KERNEL_CS32 KERNEL_CS -#define KERNEL_DS32 KERNEL_DS +#define USER_CS USER_CS32 #endif -#define TSS_INTR 0x50 -#define FIRST_SPARE_SEL 0x58 + +#define FIRST_SPARE_SEL 0x50 #define TSS_MAIN 0x80 typedef struct { @@ -153,7 +190,7 @@ void set_idt_dpl(int vec, u16 dpl); void set_gdt_entry(int sel, u32 base, u32 limit, u8 access, u8 gran); void set_intr_alt_stack(int e, void *fn); void print_current_tss_info(void); -void handle_exception(u8 v, void (*func)(struct ex_regs *regs)); +handler handle_exception(u8 v, handler fn); bool test_for_exception(unsigned int ex, void (*trigger_func)(void *data), void *data); diff --git a/tests/kvm-unit-tests/lib/x86/fwcfg.c b/tests/kvm-unit-tests/lib/x86/fwcfg.c index e2cdd157..c52b4452 100644 --- a/tests/kvm-unit-tests/lib/x86/fwcfg.c +++ b/tests/kvm-unit-tests/lib/x86/fwcfg.c @@ -3,7 +3,7 @@ static struct spinlock lock; -uint64_t fwcfg_get_u(uint16_t index, int bytes) +static uint64_t fwcfg_get_u(uint16_t index, int bytes) { uint64_t r = 0; uint8_t b; diff --git a/tests/kvm-unit-tests/lib/x86/intel-iommu.c b/tests/kvm-unit-tests/lib/x86/intel-iommu.c index 7cc5a702..9fdbd3ba 100644 --- a/tests/kvm-unit-tests/lib/x86/intel-iommu.c +++ b/tests/kvm-unit-tests/lib/x86/intel-iommu.c @@ -115,9 +115,9 @@ static void vtd_dump_init_info(void) /* Major version >= 1 */ assert(((version >> 3) & 0xf) >= 1); - printf("VT-d version: 0x%x\n", version); - printf(" cap: 0x%016lx\n", vtd_readq(DMAR_CAP_REG)); - printf(" ecap: 0x%016lx\n", vtd_readq(DMAR_ECAP_REG)); + printf("VT-d version: %#x\n", version); + printf(" cap: %#018lx\n", vtd_readq(DMAR_CAP_REG)); + printf(" ecap: %#018lx\n", vtd_readq(DMAR_ECAP_REG)); } static void vtd_setup_root_table(void) @@ -127,7 +127,7 @@ static void vtd_setup_root_table(void) memset(root, 0, PAGE_SIZE); vtd_writeq(DMAR_RTADDR_REG, virt_to_phys(root)); vtd_gcmd_or(VTD_GCMD_ROOT); - printf("DMAR table address: 0x%016lx\n", vtd_root_table()); + printf("DMAR table address: %#018lx\n", vtd_root_table()); } static void vtd_setup_ir_table(void) @@ -138,7 +138,7 @@ static void vtd_setup_ir_table(void) /* 0xf stands for table size (2^(0xf+1) == 65536) */ vtd_writeq(DMAR_IRTA_REG, virt_to_phys(root) | 0xf); vtd_gcmd_or(VTD_GCMD_IR_TABLE); - printf("IR table address: 0x%016lx\n", vtd_ir_table()); + printf("IR table address: %#018lx\n", vtd_ir_table()); } static void vtd_install_pte(vtd_pte_t *root, iova_t iova, @@ -219,14 +219,14 @@ void vtd_map_range(uint16_t sid, iova_t iova, phys_addr_t pa, size_t size) ce->present = 1; /* No error reporting yet */ ce->disable_fault_report = 1; - printf("allocated vt-d context entry for devfn 0x%x\n", + printf("allocated vt-d context entry for devfn %#x\n", devfn); } else slptptr = phys_to_virt(ce->slptptr << VTD_PAGE_SHIFT); while (size) { /* TODO: currently we only map 4K pages (level = 1) */ - printf("map 4K page IOVA 0x%lx to 0x%lx (sid=0x%04x)\n", + printf("map 4K page IOVA %#lx to %#lx (sid=%#06x)\n", iova, pa, sid); vtd_install_pte(slptptr, iova, pa, 1); size -= VTD_PAGE_SIZE; @@ -324,7 +324,7 @@ bool vtd_setup_msi(struct pci_dev *dev, int vector, int dest_id) msi_addr.head = 0xfee; msi_data.subhandle = 0; - printf("%s: msi_addr=0x%" PRIx64 ", msi_data=0x%x\n", __func__, + printf("%s: msi_addr=%#" PRIx64 ", msi_data=%#x\n", __func__, *(uint64_t *)&msi_addr, *(uint32_t *)&msi_data); return pci_setup_msi(dev, *(uint64_t *)&msi_addr, diff --git a/tests/kvm-unit-tests/lib/x86/processor.h b/tests/kvm-unit-tests/lib/x86/processor.h index 895d992a..e658d83a 100644 --- a/tests/kvm-unit-tests/lib/x86/processor.h +++ b/tests/kvm-unit-tests/lib/x86/processor.h @@ -30,13 +30,18 @@ #define X86_CR4_SMAP 0x00200000 #define X86_CR4_PKE 0x00400000 -#define X86_EFLAGS_CF 0x00000001 -#define X86_EFLAGS_PF 0x00000004 -#define X86_EFLAGS_AF 0x00000010 -#define X86_EFLAGS_ZF 0x00000040 -#define X86_EFLAGS_SF 0x00000080 -#define X86_EFLAGS_OF 0x00000800 -#define X86_EFLAGS_AC 0x00040000 +#define X86_EFLAGS_CF 0x00000001 +#define X86_EFLAGS_FIXED 0x00000002 +#define X86_EFLAGS_PF 0x00000004 +#define X86_EFLAGS_AF 0x00000010 +#define X86_EFLAGS_ZF 0x00000040 +#define X86_EFLAGS_SF 0x00000080 +#define X86_EFLAGS_TF 0x00000100 +#define X86_EFLAGS_IF 0x00000200 +#define X86_EFLAGS_DF 0x00000400 +#define X86_EFLAGS_OF 0x00000800 +#define X86_EFLAGS_NT 0x00004000 +#define X86_EFLAGS_AC 0x00040000 #define X86_IA32_EFER 0xc0000080 #define X86_EFER_LMA (1UL << 8) @@ -430,4 +435,9 @@ static inline void write_pkru(u32 pkru) : : "a" (eax), "c" (ecx), "d" (edx)); } +static inline bool is_canonical(u64 addr) +{ + return (s64)(addr << 16) >> 16 == addr; +} + #endif diff --git a/tests/kvm-unit-tests/lib/x86/smp.c b/tests/kvm-unit-tests/lib/x86/smp.c index 1eb49f24..bffb6dec 100644 --- a/tests/kvm-unit-tests/lib/x86/smp.c +++ b/tests/kvm-unit-tests/lib/x86/smp.c @@ -1,5 +1,7 @@ #include +#include "processor.h" +#include "atomic.h" #include "smp.h" #include "apic.h" #include "fwcfg.h" @@ -15,6 +17,7 @@ static void *volatile ipi_data; static volatile int ipi_done; static volatile bool ipi_wait; static int _cpu_count; +static atomic_t active_cpus; static __attribute__((used)) void ipi() { @@ -27,6 +30,7 @@ static __attribute__((used)) void ipi() apic_write(APIC_EOI, 0); } function(data); + atomic_dec(&active_cpus); if (wait) { ipi_done = 1; apic_write(APIC_EOI, 0); @@ -43,22 +47,6 @@ asm ( #endif ); -void spin_lock(struct spinlock *lock) -{ - int v = 1; - - do { - asm volatile ("xchg %1, %0" : "+m"(lock->v), "+r"(v)); - } while (v); - asm volatile ("" : : : "memory"); -} - -void spin_unlock(struct spinlock *lock) -{ - asm volatile ("" : : : "memory"); - lock->v = 0; -} - int cpu_count(void) { return _cpu_count; @@ -84,6 +72,7 @@ static void __on_cpu(int cpu, void (*function)(void *data), void *data, if (cpu == smp_id()) function(data); else { + atomic_inc(&active_cpus); ipi_done = 0; ipi_function = function; ipi_data = data; @@ -107,6 +96,21 @@ void on_cpu_async(int cpu, void (*function)(void *data), void *data) __on_cpu(cpu, function, data, 0); } +void on_cpus(void (*function)(void *data), void *data) +{ + int cpu; + + for (cpu = cpu_count() - 1; cpu >= 0; --cpu) + on_cpu_async(cpu, function, data); + + while (cpus_active() > 1) + pause(); +} + +int cpus_active(void) +{ + return atomic_read(&active_cpus); +} void smp_init(void) { @@ -122,4 +126,5 @@ void smp_init(void) for (i = 1; i < cpu_count(); ++i) on_cpu(i, setup_smp_id, 0); + atomic_inc(&active_cpus); } diff --git a/tests/kvm-unit-tests/lib/x86/smp.h b/tests/kvm-unit-tests/lib/x86/smp.h index afabac84..1453bb5e 100644 --- a/tests/kvm-unit-tests/lib/x86/smp.h +++ b/tests/kvm-unit-tests/lib/x86/smp.h @@ -6,7 +6,9 @@ void smp_init(void); int cpu_count(void); int smp_id(void); +int cpus_active(void); void on_cpu(int cpu, void (*function)(void *data), void *data); void on_cpu_async(int cpu, void (*function)(void *data), void *data); +void on_cpus(void (*function)(void *data), void *data); #endif diff --git a/tests/kvm-unit-tests/lib/x86/vm.c b/tests/kvm-unit-tests/lib/x86/vm.c index cda4c5f4..9b5f9220 100644 --- a/tests/kvm-unit-tests/lib/x86/vm.c +++ b/tests/kvm-unit-tests/lib/x86/vm.c @@ -7,12 +7,29 @@ static void *vfree_top = 0; static void free_memory(void *mem, unsigned long size) { - while (size >= PAGE_SIZE) { - *(void **)mem = free; + void *end; + + assert_msg((unsigned long) mem % PAGE_SIZE == 0, + "mem not page aligned: %p", mem); + + assert_msg(size % PAGE_SIZE == 0, "size not page aligned: %#lx", size); + + assert_msg(size == 0 || mem + size > mem, + "mem + size overflow: %p + %#lx", mem, size); + + if (size == 0) { + free = NULL; + return; + } + free = mem; - mem += PAGE_SIZE; - size -= PAGE_SIZE; - } + end = mem + size; + while (mem + PAGE_SIZE != end) { + *(void **)mem = (mem + PAGE_SIZE); + mem += PAGE_SIZE; + } + + *(void **)mem = NULL; } void *alloc_page() @@ -28,6 +45,63 @@ void *alloc_page() return p; } +/* + * Allocates (1 << order) physically contiguous and naturally aligned pages. + * Returns NULL if there's no memory left. + */ +void *alloc_pages(unsigned long order) +{ + /* Generic list traversal. */ + void *prev; + void *curr = NULL; + void *next = free; + + /* Looking for a run of length (1 << order). */ + unsigned long run = 0; + const unsigned long n = 1ul << order; + const unsigned long align_mask = (n << PAGE_SHIFT) - 1; + void *run_start = NULL; + void *run_prev = NULL; + unsigned long run_next_pa = 0; + unsigned long pa; + + assert(order < sizeof(unsigned long) * 8); + + for (;;) { + prev = curr; + curr = next; + next = curr ? *((void **) curr) : NULL; + + if (!curr) + return 0; + + pa = virt_to_phys(curr); + + if (run == 0) { + if (!(pa & align_mask)) { + run_start = curr; + run_prev = prev; + run_next_pa = pa + PAGE_SIZE; + run = 1; + } + } else if (pa == run_next_pa) { + run_next_pa += PAGE_SIZE; + run += 1; + } else { + run = 0; + } + + if (run == n) { + if (run_prev) + *((void **) run_prev) = next; + else + free = next; + return run_start; + } + } +} + + void free_page(void *page) { *(void **)page = free; @@ -65,23 +139,62 @@ unsigned long *install_pte(unsigned long *cr3, return &pt[offset]; } +/* + * Finds last PTE in the mapping of @virt that's at or above @lowest_level. The + * returned PTE isn't necessarily present, but its parent is. + */ +struct pte_search find_pte_level(unsigned long *cr3, void *virt, + int lowest_level) +{ + unsigned long *pt = cr3, pte; + unsigned offset; + unsigned long shift; + struct pte_search r; + + assert(lowest_level >= 1 && lowest_level <= PAGE_LEVEL); + + for (r.level = PAGE_LEVEL;; --r.level) { + shift = (r.level - 1) * PGDIR_WIDTH + 12; + offset = ((unsigned long)virt >> shift) & PGDIR_MASK; + r.pte = &pt[offset]; + pte = *r.pte; + + if (!(pte & PT_PRESENT_MASK)) + return r; + + if ((r.level == 2 || r.level == 3) && (pte & PT_PAGE_SIZE_MASK)) + return r; + + if (r.level == lowest_level) + return r; + + pt = phys_to_virt(pte & 0xffffffffff000ull); + } +} + +/* + * Returns the leaf PTE in the mapping of @virt (i.e., 4K PTE or a present huge + * PTE). Returns NULL if no leaf PTE exists. + */ unsigned long *get_pte(unsigned long *cr3, void *virt) { - int level; - unsigned long *pt = cr3, pte; - unsigned offset; + struct pte_search search; - for (level = PAGE_LEVEL; level > 1; --level) { - offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK; - pte = pt[offset]; - if (!(pte & PT_PRESENT_MASK)) - return NULL; - if (level == 2 && (pte & PT_PAGE_SIZE_MASK)) - return &pt[offset]; - pt = phys_to_virt(pte & PT_ADDR_MASK); - } - offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK; - return &pt[offset]; + search = find_pte_level(cr3, virt, 1); + return found_leaf_pte(search) ? search.pte : NULL; +} + +/* + * Returns the PTE in the mapping of @virt at the given level @pte_level. + * Returns NULL if the PT at @pte_level isn't present (i.e., the mapping at + * @pte_level - 1 isn't present). + */ +unsigned long *get_pte_level(unsigned long *cr3, void *virt, int pte_level) +{ + struct pte_search search; + + search = find_pte_level(cr3, virt, pte_level); + return search.level == pte_level ? search.pte : NULL; } unsigned long *install_large_page(unsigned long *cr3, @@ -99,6 +212,33 @@ unsigned long *install_page(unsigned long *cr3, return install_pte(cr3, 1, virt, phys | PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK, 0); } +void install_pages(unsigned long *cr3, unsigned long phys, unsigned long len, + void *virt) +{ + unsigned long max = (u64)len + (u64)phys; + assert(phys % PAGE_SIZE == 0); + assert((unsigned long) virt % PAGE_SIZE == 0); + assert(len % PAGE_SIZE == 0); + + while (phys + PAGE_SIZE <= max) { + install_page(cr3, phys, virt); + phys += PAGE_SIZE; + virt = (char *) virt + PAGE_SIZE; + } +} + +bool any_present_pages(unsigned long *cr3, void *virt, unsigned long len) +{ + unsigned long max = (unsigned long) virt + len; + unsigned long curr; + + for (curr = (unsigned long) virt; curr < max; curr += PAGE_SIZE) { + unsigned long *ptep = get_pte(cr3, (void *) curr); + if (ptep && (*ptep & PT_PRESENT_MASK)) + return true; + } + return false; +} static void setup_mmu_range(unsigned long *cr3, unsigned long start, unsigned long len) @@ -110,10 +250,7 @@ static void setup_mmu_range(unsigned long *cr3, unsigned long start, install_large_page(cr3, phys, (void *)(ulong)phys); phys += LARGE_PAGE_SIZE; } - while (phys + PAGE_SIZE <= max) { - install_page(cr3, phys, (void *)(ulong)phys); - phys += PAGE_SIZE; - } + install_pages(cr3, phys, max - phys, (void *)(ulong)phys); } static void setup_mmu(unsigned long len) diff --git a/tests/kvm-unit-tests/lib/x86/vm.h b/tests/kvm-unit-tests/lib/x86/vm.h index 6a4384f5..3522ba8b 100644 --- a/tests/kvm-unit-tests/lib/x86/vm.h +++ b/tests/kvm-unit-tests/lib/x86/vm.h @@ -14,7 +14,27 @@ void *alloc_vpage(void); void *alloc_vpages(ulong nr); uint64_t virt_to_phys_cr3(void *mem); +struct pte_search { + int level; + unsigned long *pte; +}; + +static inline bool found_huge_pte(struct pte_search search) +{ + return (search.level == 2 || search.level == 3) && + (*search.pte & PT_PRESENT_MASK) && + (*search.pte & PT_PAGE_SIZE_MASK); +} + +static inline bool found_leaf_pte(struct pte_search search) +{ + return search.level == 1 || found_huge_pte(search); +} + +struct pte_search find_pte_level(unsigned long *cr3, void *virt, + int lowest_level); unsigned long *get_pte(unsigned long *cr3, void *virt); +unsigned long *get_pte_level(unsigned long *cr3, void *virt, int pte_level); unsigned long *install_pte(unsigned long *cr3, int pte_level, void *virt, @@ -22,10 +42,18 @@ unsigned long *install_pte(unsigned long *cr3, unsigned long *pt_page); void *alloc_page(); +void *alloc_pages(unsigned long order); void free_page(void *page); unsigned long *install_large_page(unsigned long *cr3,unsigned long phys, void *virt); unsigned long *install_page(unsigned long *cr3, unsigned long phys, void *virt); +void install_pages(unsigned long *cr3, unsigned long phys, unsigned long len, + void *virt); +bool any_present_pages(unsigned long *cr3, void *virt, unsigned long len); +static inline void *current_page_table(void) +{ + return phys_to_virt(read_cr3()); +} #endif diff --git a/tests/kvm-unit-tests/x86/Makefile b/tests/kvm-unit-tests/x86/Makefile index 369a38b2..8a007ab5 100644 --- a/tests/kvm-unit-tests/x86/Makefile +++ b/tests/kvm-unit-tests/x86/Makefile @@ -1 +1 @@ -include $(TEST_DIR)/Makefile.$(ARCH) +include $(SRCDIR)/$(TEST_DIR)/Makefile.$(ARCH) diff --git a/tests/kvm-unit-tests/x86/Makefile.common b/tests/kvm-unit-tests/x86/Makefile.common index fbab82c8..e96812bf 100644 --- a/tests/kvm-unit-tests/x86/Makefile.common +++ b/tests/kvm-unit-tests/x86/Makefile.common @@ -1,6 +1,6 @@ #This is a make file with common rules for both x86 & x86-64 -all: test_cases +all: directories test_cases cflatobjs += lib/pci.o cflatobjs += lib/pci-edu.o @@ -16,11 +16,13 @@ cflatobjs += lib/x86/isr.o cflatobjs += lib/x86/acpi.o cflatobjs += lib/x86/stack.o -$(libcflat): LDFLAGS += -nostdlib -$(libcflat): CFLAGS += -ffreestanding -I lib +OBJDIRS += lib/x86 -CFLAGS += -m$(bits) -CFLAGS += -O1 +$(libcflat): LDFLAGS += -nostdlib +$(libcflat): CFLAGS += -ffreestanding -I $(SRCDIR)/lib -I lib + +COMMON_CFLAGS += -m$(bits) +COMMON_CFLAGS += -O1 # stack.o relies on frame pointers. KEEP_FRAME_POINTER := y @@ -31,8 +33,8 @@ libgcc := $(shell $(CC) -m$(bits) --print-libgcc-file-name) .PRECIOUS: %.elf %.o FLATLIBS = lib/libcflat.a $(libgcc) -%.elf: %.o $(FLATLIBS) x86/flat.lds $(cstart.o) - $(CC) $(CFLAGS) -nostdlib -o $@ -Wl,-T,x86/flat.lds \ +%.elf: %.o $(FLATLIBS) $(SRCDIR)/x86/flat.lds $(cstart.o) + $(CC) $(CFLAGS) -nostdlib -o $@ -Wl,-T,$(SRCDIR)/x86/flat.lds \ $(filter %.o, $^) $(FLATLIBS) %.flat: %.elf @@ -47,19 +49,20 @@ tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \ $(TEST_DIR)/tsc_adjust.flat $(TEST_DIR)/asyncpf.flat \ $(TEST_DIR)/init.flat $(TEST_DIR)/smap.flat \ $(TEST_DIR)/hyperv_synic.flat $(TEST_DIR)/hyperv_stimer.flat \ + $(TEST_DIR)/hyperv_connections.flat \ ifdef API -tests-common += api/api-sample -tests-common += api/dirty-log -tests-common += api/dirty-log-perf +tests-api = api/api-sample api/dirty-log api/dirty-log-perf + +OBJDIRS += api endif -test_cases: $(tests-common) $(tests) +test_cases: $(tests-common) $(tests) $(tests-api) -$(TEST_DIR)/%.o: CFLAGS += -std=gnu99 -ffreestanding -I lib -I lib/x86 +$(TEST_DIR)/%.o: CFLAGS += -std=gnu99 -ffreestanding -I $(SRCDIR)/lib -I $(SRCDIR)/lib/x86 -I lib $(TEST_DIR)/realmode.elf: $(TEST_DIR)/realmode.o - $(CC) -m32 -nostdlib -o $@ -Wl,-T,$(TEST_DIR)/realmode.lds $^ + $(CC) -m32 -nostdlib -o $@ -Wl,-T,$(SRCDIR)/$(TEST_DIR)/realmode.lds $^ $(TEST_DIR)/realmode.o: bits = 32 @@ -69,20 +72,19 @@ $(TEST_DIR)/hyperv_synic.elf: $(TEST_DIR)/hyperv.o $(TEST_DIR)/hyperv_stimer.elf: $(TEST_DIR)/hyperv.o +$(TEST_DIR)/hyperv_connections.elf: $(TEST_DIR)/hyperv.o + arch_clean: $(RM) $(TEST_DIR)/*.o $(TEST_DIR)/*.flat $(TEST_DIR)/*.elf \ - $(TEST_DIR)/.*.d lib/x86/.*.d + $(TEST_DIR)/.*.d lib/x86/.*.d \ + $(tests-api) api/*.o api/*.a api/.*.d -api/%.o: CFLAGS += -m32 +api/%.o: CXXFLAGS += -m32 -std=gnu++11 -api/%: LDLIBS += -lstdc++ -lboost_thread -lpthread -lrt +api/%: LDLIBS += -lstdc++ -lpthread -lrt api/%: LDFLAGS += -m32 api/libapi.a: api/kvmxx.o api/identity.o api/exception.o api/memmap.o $(AR) rcs $@ $^ -api/api-sample: api/api-sample.o api/libapi.a - -api/dirty-log: api/dirty-log.o api/libapi.a - -api/dirty-log-perf: api/dirty-log-perf.o api/libapi.a +$(tests-api) : % : %.o api/libapi.a diff --git a/tests/kvm-unit-tests/x86/Makefile.i386 b/tests/kvm-unit-tests/x86/Makefile.i386 index 5f89e3e5..c105cac6 100644 --- a/tests/kvm-unit-tests/x86/Makefile.i386 +++ b/tests/kvm-unit-tests/x86/Makefile.i386 @@ -30,4 +30,4 @@ tests += $(TEST_DIR)/tscdeadline_latency.flat #tests += $(TEST_DIR)/intel-iommu.flat -include $(TEST_DIR)/Makefile.common +include $(SRCDIR)/$(TEST_DIR)/Makefile.common diff --git a/tests/kvm-unit-tests/x86/Makefile.x86_64 b/tests/kvm-unit-tests/x86/Makefile.x86_64 index 3e2821ea..623fc5b3 100644 --- a/tests/kvm-unit-tests/x86/Makefile.x86_64 +++ b/tests/kvm-unit-tests/x86/Makefile.x86_64 @@ -1,7 +1,7 @@ cstart.o = $(TEST_DIR)/cstart64.o bits = 64 ldarch = elf64-x86-64 -CFLAGS += -mno-red-zone +COMMON_CFLAGS += -mno-red-zone cflatobjs += lib/x86/setjmp64.o cflatobjs += lib/x86/intel-iommu.o @@ -12,12 +12,13 @@ tests = $(TEST_DIR)/access.flat $(TEST_DIR)/apic.flat \ $(TEST_DIR)/pcid.flat $(TEST_DIR)/debug.flat \ $(TEST_DIR)/ioapic.flat $(TEST_DIR)/memory.flat \ $(TEST_DIR)/pku.flat $(TEST_DIR)/hyperv_clock.flat +tests += $(TEST_DIR)/syscall.flat tests += $(TEST_DIR)/svm.flat tests += $(TEST_DIR)/vmx.flat tests += $(TEST_DIR)/tscdeadline_latency.flat tests += $(TEST_DIR)/intel-iommu.flat -include $(TEST_DIR)/Makefile.common +include $(SRCDIR)/$(TEST_DIR)/Makefile.common $(TEST_DIR)/hyperv_clock.elf: $(TEST_DIR)/hyperv_clock.o diff --git a/tests/kvm-unit-tests/x86/asyncpf.c b/tests/kvm-unit-tests/x86/asyncpf.c index e29e07c5..f04d7cb5 100644 --- a/tests/kvm-unit-tests/x86/asyncpf.c +++ b/tests/kvm-unit-tests/x86/asyncpf.c @@ -52,13 +52,13 @@ static void pf_isr(struct ex_regs *r) switch (reason) { case 0: - report("unexpected #PF at %p", false, read_cr2()); + report("unexpected #PF at %#lx", false, read_cr2()); break; case KVM_PV_REASON_PAGE_NOT_PRESENT: phys = virt_to_phys_cr3(virt); install_pte(phys_to_virt(read_cr3()), 1, virt, phys, 0); write_cr3(read_cr3()); - report("Got not present #PF token %x virt addr %p phys addr %p", + report("Got not present #PF token %lx virt addr %p phys addr %#" PRIx64, true, read_cr2(), virt, phys); while(phys) { safe_halt(); /* enables irq */ @@ -66,7 +66,7 @@ static void pf_isr(struct ex_regs *r) } break; case KVM_PV_REASON_PAGE_READY: - report("Got present #PF token %x", true, read_cr2()); + report("Got present #PF token %lx", true, read_cr2()); if ((uint32_t)read_cr2() == ~0) break; install_pte(phys_to_virt(read_cr3()), 1, virt, phys | PT_PRESENT_MASK | PT_WRITABLE_MASK, 0); diff --git a/tests/kvm-unit-tests/x86/cstart.S b/tests/kvm-unit-tests/x86/cstart.S index 69b5c332..a3c26a41 100644 --- a/tests/kvm-unit-tests/x86/cstart.S +++ b/tests/kvm-unit-tests/x86/cstart.S @@ -34,14 +34,14 @@ gdt32: .quad 0x00cf9b000000ffff // flat 32-bit code segment .quad 0x00cf93000000ffff // flat 32-bit data segment .quad 0x00cf1b000000ffff // flat 32-bit code segment, not present - .quad 0x00cffb000000ffff // 64-bit code segment (user) - .quad 0x00cff3000000ffff // 64-bit data segment (user) + .quad 0 // TSS for task gates + .quad 0x008f9b000000FFFF // 16-bit code segment + .quad 0x008f93000000FFFF // 16-bit data segment + .quad 0x00cffb000000ffff // 32-bit code segment (user) + .quad 0x00cff3000000ffff // 32-bit data segment (user) + .quad 0 // unused - .quad 0 // 10 spare selectors - .quad 0 - .quad 0 - .quad 0 - .quad 0 + .quad 0 // 6 spare selectors .quad 0 .quad 0 .quad 0 diff --git a/tests/kvm-unit-tests/x86/cstart64.S b/tests/kvm-unit-tests/x86/cstart64.S index 004c014b..4c26fb25 100644 --- a/tests/kvm-unit-tests/x86/cstart64.S +++ b/tests/kvm-unit-tests/x86/cstart64.S @@ -53,14 +53,14 @@ gdt64_desc: gdt64: .quad 0 .quad 0x00af9b000000ffff // 64-bit code segment - .quad 0x00cf93000000ffff // 64-bit data segment + .quad 0x00cf93000000ffff // 32/64-bit data segment .quad 0x00af1b000000ffff // 64-bit code segment, not present - .quad 0x00affb000000ffff // 64-bit code segment (user) - .quad 0x00cff3000000ffff // 64-bit data segment (user) .quad 0x00cf9b000000ffff // 32-bit code segment - .quad 0x00cf92000000ffff // 32-bit data segment - .quad 0x008F9A000000FFFF // 16-bit code segment - .quad 0x008F92000000FFFF // 16-bit data segment + .quad 0x008f9b000000FFFF // 16-bit code segment + .quad 0x008f93000000FFFF // 16-bit data segment + .quad 0x00cffb000000ffff // 32-bit code segment (user) + .quad 0x00cff3000000ffff // 32/64-bit data segment (user) + .quad 0x00affb000000ffff // 64-bit code segment (user) .quad 0 // 6 spare selectors .quad 0 diff --git a/tests/kvm-unit-tests/x86/eventinj.c b/tests/kvm-unit-tests/x86/eventinj.c index 9ee557b8..665eb618 100644 --- a/tests/kvm-unit-tests/x86/eventinj.c +++ b/tests/kvm-unit-tests/x86/eventinj.c @@ -171,7 +171,7 @@ static void nmi_iret_isr(struct ex_regs *r) static void tirq0(isr_regs_t *r) { printf("irq0 running\n"); - if (test_count != 0) + if (test_count == 1) test_count++; eoi(); } diff --git a/tests/kvm-unit-tests/x86/hyperv.c b/tests/kvm-unit-tests/x86/hyperv.c index 2511aa2e..60f76455 100644 --- a/tests/kvm-unit-tests/x86/hyperv.c +++ b/tests/kvm-unit-tests/x86/hyperv.c @@ -1,25 +1,70 @@ #include "hyperv.h" #include "asm/io.h" +#include "smp.h" -static void synic_ctl(u8 ctl, u8 vcpu_id, u8 sint) +enum { + HV_TEST_DEV_SINT_ROUTE_CREATE = 1, + HV_TEST_DEV_SINT_ROUTE_DESTROY, + HV_TEST_DEV_SINT_ROUTE_SET_SINT, + HV_TEST_DEV_MSG_CONN_CREATE, + HV_TEST_DEV_MSG_CONN_DESTROY, + HV_TEST_DEV_EVT_CONN_CREATE, + HV_TEST_DEV_EVT_CONN_DESTROY, +}; + +static void synic_ctl(u32 ctl, u32 vcpu_id, u32 sint, u32 conn_id) { - outl((ctl << 16)|((vcpu_id) << 8)|sint, 0x3000); + outl((conn_id << 24) | (ctl << 16) | (vcpu_id << 8) | sint, 0x3000); } -void synic_sint_create(int vcpu, int sint, int vec, bool auto_eoi) +static void sint_enable(u8 sint, u8 vec, bool auto_eoi) { wrmsr(HV_X64_MSR_SINT0 + sint, - (u64)vec | ((auto_eoi) ? HV_SYNIC_SINT_AUTO_EOI : 0)); - synic_ctl(HV_TEST_DEV_SINT_ROUTE_CREATE, vcpu, sint); + (u64)vec | (auto_eoi ? HV_SYNIC_SINT_AUTO_EOI : 0)); } -void synic_sint_set(int vcpu, int sint) +static void sint_disable(u8 sint) { - synic_ctl(HV_TEST_DEV_SINT_ROUTE_SET_SINT, vcpu, sint); + wrmsr(HV_X64_MSR_SINT0 + sint, 0xff | HV_SYNIC_SINT_MASKED); } -void synic_sint_destroy(int vcpu, int sint) +void synic_sint_create(u8 sint, u8 vec, bool auto_eoi) { - wrmsr(HV_X64_MSR_SINT0 + sint, 0xFF|HV_SYNIC_SINT_MASKED); - synic_ctl(HV_TEST_DEV_SINT_ROUTE_DESTROY, vcpu, sint); + synic_ctl(HV_TEST_DEV_SINT_ROUTE_CREATE, smp_id(), sint, 0); + sint_enable(sint, vec, auto_eoi); +} + +void synic_sint_set(u8 vcpu, u8 sint) +{ + synic_ctl(HV_TEST_DEV_SINT_ROUTE_SET_SINT, vcpu, sint, 0); +} + +void synic_sint_destroy(u8 sint) +{ + sint_disable(sint); + synic_ctl(HV_TEST_DEV_SINT_ROUTE_DESTROY, smp_id(), sint, 0); +} + +void msg_conn_create(u8 sint, u8 vec, u8 conn_id) +{ + synic_ctl(HV_TEST_DEV_MSG_CONN_CREATE, smp_id(), sint, conn_id); + sint_enable(sint, vec, true); +} + +void msg_conn_destroy(u8 sint, u8 conn_id) +{ + sint_disable(sint); + synic_ctl(HV_TEST_DEV_MSG_CONN_DESTROY, 0, 0, conn_id); +} + +void evt_conn_create(u8 sint, u8 vec, u8 conn_id) +{ + synic_ctl(HV_TEST_DEV_EVT_CONN_CREATE, smp_id(), sint, conn_id); + sint_enable(sint, vec, true); +} + +void evt_conn_destroy(u8 sint, u8 conn_id) +{ + sint_disable(sint); + synic_ctl(HV_TEST_DEV_EVT_CONN_DESTROY, 0, 0, conn_id); } diff --git a/tests/kvm-unit-tests/x86/hyperv.h b/tests/kvm-unit-tests/x86/hyperv.h index bef03177..e135221f 100644 --- a/tests/kvm-unit-tests/x86/hyperv.h +++ b/tests/kvm-unit-tests/x86/hyperv.h @@ -10,6 +10,9 @@ #define HV_X64_MSR_SYNIC_AVAILABLE (1 << 2) #define HV_X64_MSR_SYNTIMER_AVAILABLE (1 << 3) +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 +#define HV_X64_MSR_HYPERCALL 0x40000001 + #define HV_X64_MSR_TIME_REF_COUNT 0x40000020 #define HV_X64_MSR_REFERENCE_TSC 0x40000021 @@ -155,10 +158,29 @@ struct hv_message_page { struct hv_message sint_message[HV_SYNIC_SINT_COUNT]; }; -enum { - HV_TEST_DEV_SINT_ROUTE_CREATE = 1, - HV_TEST_DEV_SINT_ROUTE_DESTROY, - HV_TEST_DEV_SINT_ROUTE_SET_SINT +#define HV_EVENT_FLAGS_COUNT (256 * 8) + +struct hv_event_flags { + ulong flags[HV_EVENT_FLAGS_COUNT / (8 * sizeof(ulong))]; +}; + +struct hv_event_flags_page { + struct hv_event_flags slot[HV_SYNIC_SINT_COUNT]; +}; + +#define HV_X64_MSR_HYPERCALL_ENABLE 0x1 + +#define HV_HYPERCALL_FAST (1u << 16) + +#define HVCALL_POST_MESSAGE 0x5c +#define HVCALL_SIGNAL_EVENT 0x5d + +struct hv_input_post_message { + u32 connectionid; + u32 reserved; + u32 message_type; + u32 payload_size; + u64 payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT]; }; static inline bool synic_supported(void) @@ -176,9 +198,13 @@ static inline bool hv_time_ref_counter_supported(void) return cpuid(HYPERV_CPUID_FEATURES).a & HV_X64_MSR_TIME_REF_COUNT_AVAILABLE; } -void synic_sint_create(int vcpu, int sint, int vec, bool auto_eoi); -void synic_sint_set(int vcpu, int sint); -void synic_sint_destroy(int vcpu, int sint); +void synic_sint_create(u8 sint, u8 vec, bool auto_eoi); +void synic_sint_set(u8 vcpu, u8 sint); +void synic_sint_destroy(u8 sint); +void msg_conn_create(u8 sint, u8 vec, u8 conn_id); +void msg_conn_destroy(u8 sint, u8 conn_id); +void evt_conn_create(u8 sint, u8 vec, u8 conn_id); +void evt_conn_destroy(u8 sint, u8 conn_id); struct hv_reference_tsc_page { uint32_t tsc_sequence; diff --git a/tests/kvm-unit-tests/x86/hyperv_clock.c b/tests/kvm-unit-tests/x86/hyperv_clock.c index 8b1deba6..94972697 100644 --- a/tests/kvm-unit-tests/x86/hyperv_clock.c +++ b/tests/kvm-unit-tests/x86/hyperv_clock.c @@ -19,7 +19,7 @@ static inline u64 scale_delta(u64 delta, u64 mul_frac) u64 product, unused; __asm__ ( - "mul %3" + "mulq %3" : "=d" (product), "=a" (unused) : "1" (delta), "rm" ((u64)mul_frac) ); return product; @@ -55,7 +55,6 @@ uint64_t hv_clock_read(void) return hvclock_tsc_to_ticks(&shadow, rdtsc()); } -atomic_t cpus_left; bool ok[MAX_CPU]; uint64_t loops[MAX_CPU]; @@ -99,7 +98,6 @@ static void hv_clock_test(void *data) if (!got_drift) printf("delta on CPU %d was %d...%d\n", smp_id(), min_delta, max_delta); barrier(); - atomic_dec(&cpus_left); } static void check_test(int ncpus) @@ -107,13 +105,7 @@ static void check_test(int ncpus) int i; bool pass; - atomic_set(&cpus_left, ncpus); - for (i = ncpus - 1; i >= 0; i--) - on_cpu_async(i, hv_clock_test, NULL); - - /* Wait for the end of other vcpu */ - while(atomic_read(&cpus_left)) - ; + on_cpus(hv_clock_test, NULL); pass = true; for (i = ncpus - 1; i >= 0; i--) @@ -134,7 +126,6 @@ static void hv_perf_test(void *data) } while(t < end); loops[smp_id()] = local_loops; - atomic_dec(&cpus_left); } static void perf_test(int ncpus) @@ -142,13 +133,7 @@ static void perf_test(int ncpus) int i; uint64_t total_loops; - atomic_set(&cpus_left, ncpus); - for (i = ncpus - 1; i >= 0; i--) - on_cpu_async(i, hv_perf_test, NULL); - - /* Wait for the end of other vcpu */ - while(atomic_read(&cpus_left)) - ; + on_cpus(hv_perf_test, NULL); total_loops = 0; for (i = ncpus - 1; i >= 0; i--) @@ -167,6 +152,10 @@ int main(int ac, char **av) setup_vm(); smp_init(); + ncpus = cpu_count(); + if (ncpus > MAX_CPU) + report_abort("number cpus exceeds %d", MAX_CPU); + hv_clock = alloc_page(); wrmsr(HV_X64_MSR_REFERENCE_TSC, (u64)(uintptr_t)hv_clock | 1); report("MSR value after enabling", @@ -195,10 +184,6 @@ int main(int ac, char **av) "TSC reference %" PRId64" (delta %" PRId64")\n", ref2, ref2 - ref1, tsc2, t2, t2 - t1); - ncpus = cpu_count(); - if (ncpus > MAX_CPU) - ncpus = MAX_CPU; - check_test(ncpus); perf_test(ncpus); diff --git a/tests/kvm-unit-tests/x86/hyperv_stimer.c b/tests/kvm-unit-tests/x86/hyperv_stimer.c index 52925237..bd7f9663 100644 --- a/tests/kvm-unit-tests/x86/hyperv_stimer.c +++ b/tests/kvm-unit-tests/x86/hyperv_stimer.c @@ -19,8 +19,6 @@ #define SINT2_NUM 3 #define ONE_MS_IN_100NS 10000 -static atomic_t g_cpus_comp_count; -static int g_cpus_count; static struct spinlock g_synic_alloc_lock; struct stimer { @@ -216,20 +214,13 @@ static void synic_disable(void) synic_free_page(svcpu->msg_page); } -static void cpu_comp(void) -{ - atomic_inc(&g_cpus_comp_count); -} static void stimer_test_prepare(void *ctx) { - int vcpu = smp_id(); - write_cr3((ulong)ctx); synic_enable(); - synic_sint_create(vcpu, SINT1_NUM, SINT1_VEC, false); - synic_sint_create(vcpu, SINT2_NUM, SINT2_VEC, true); - cpu_comp(); + synic_sint_create(SINT1_NUM, SINT1_VEC, false); + synic_sint_create(SINT2_NUM, SINT2_VEC, true); } static void stimer_test_periodic(int vcpu, struct stimer *timer1, @@ -280,6 +271,35 @@ static void stimer_test_auto_enable_periodic(int vcpu, struct stimer *timer) stimer_shutdown(timer); } +static void stimer_test_one_shot_busy(int vcpu, struct stimer *timer) +{ + struct hv_message_page *msg_page = g_synic_vcpu[vcpu].msg_page; + struct hv_message *msg = &msg_page->sint_message[timer->sint]; + + msg->header.message_type = HVMSG_TIMER_EXPIRED; + wmb(); + + stimer_start(timer, false, false, ONE_MS_IN_100NS, SINT1_NUM); + + do + rmb(); + while (!msg->header.message_flags.msg_pending); + + report("no timer fired while msg slot busy: vcpu %d", + !atomic_read(&timer->fire_count), vcpu); + + msg->header.message_type = HVMSG_NONE; + wmb(); + wrmsr(HV_X64_MSR_EOM, 0); + + while (atomic_read(&timer->fire_count) < 1) { + pause(); + } + report("timer resumed when msg slot released: vcpu %d", true, vcpu); + + stimer_shutdown(timer); +} + static void stimer_test(void *ctx) { int vcpu = smp_id(); @@ -295,33 +315,17 @@ static void stimer_test(void *ctx) stimer_test_one_shot(vcpu, timer1); stimer_test_auto_enable_one_shot(vcpu, timer2); stimer_test_auto_enable_periodic(vcpu, timer1); + stimer_test_one_shot_busy(vcpu, timer1); irq_disable(); - cpu_comp(); } static void stimer_test_cleanup(void *ctx) { - int vcpu = smp_id(); - stimers_shutdown(); - synic_sint_destroy(vcpu, SINT1_NUM); - synic_sint_destroy(vcpu, SINT2_NUM); + synic_sint_destroy(SINT1_NUM); + synic_sint_destroy(SINT2_NUM); synic_disable(); - cpu_comp(); -} - -static void on_each_cpu_async_wait(void (*func)(void *ctx), void *ctx) -{ - int i; - - atomic_set(&g_cpus_comp_count, 0); - for (i = 0; i < g_cpus_count; i++) { - on_cpu_async(i, func, ctx); - } - while (atomic_read(&g_cpus_comp_count) != g_cpus_count) { - pause(); - } } static void stimer_test_all(void) @@ -332,20 +336,17 @@ static void stimer_test_all(void) smp_init(); enable_apic(); + ncpus = cpu_count(); + if (ncpus > MAX_CPUS) + report_abort("number cpus exceeds %d", MAX_CPUS); + printf("cpus = %d\n", ncpus); + handle_irq(SINT1_VEC, stimer_isr); handle_irq(SINT2_VEC, stimer_isr_auto_eoi); - ncpus = cpu_count(); - if (ncpus > MAX_CPUS) { - ncpus = MAX_CPUS; - } - - printf("cpus = %d\n", ncpus); - g_cpus_count = ncpus; - - on_each_cpu_async_wait(stimer_test_prepare, (void *)read_cr3()); - on_each_cpu_async_wait(stimer_test, NULL); - on_each_cpu_async_wait(stimer_test_cleanup, NULL); + on_cpus(stimer_test_prepare, (void *)read_cr3()); + on_cpus(stimer_test, NULL); + on_cpus(stimer_test_cleanup, NULL); } int main(int ac, char **av) diff --git a/tests/kvm-unit-tests/x86/hyperv_synic.c b/tests/kvm-unit-tests/x86/hyperv_synic.c index 74bbd58e..1ccf1a06 100644 --- a/tests/kvm-unit-tests/x86/hyperv_synic.c +++ b/tests/kvm-unit-tests/x86/hyperv_synic.c @@ -12,7 +12,6 @@ #define MAX_CPUS 4 static atomic_t isr_enter_count[MAX_CPUS]; -static atomic_t cpus_comp_count; static void synic_sint_auto_eoi_isr(isr_regs_t *regs) { @@ -69,7 +68,7 @@ static void synic_sints_prepare(int vcpu) for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) { vec = sint_vecs[i].vec; auto_eoi = sint_vecs[i].auto_eoi; - synic_sint_create(vcpu, i, vec, auto_eoi); + synic_sint_create(i, vec, auto_eoi); } } @@ -90,8 +89,8 @@ static void synic_test_prepare(void *ctx) } r = rdmsr(HV_X64_MSR_EOM); if (r != 0) { - report("Hyper-V SynIC test, EOM read 0x%llx", false, r); - goto ret; + report("Hyper-V SynIC test, EOM read %#" PRIx64, false, r); + return; } wrmsr(HV_X64_MSR_SIMP, (u64)virt_to_phys(alloc_page()) | @@ -101,8 +100,6 @@ static void synic_test_prepare(void *ctx) wrmsr(HV_X64_MSR_SCONTROL, HV_SYNIC_CONTROL_ENABLE); synic_sints_prepare(smp_id()); -ret: - atomic_inc(&cpus_comp_count); } static void synic_sints_test(int dst_vcpu) @@ -125,24 +122,20 @@ static void synic_test(void *ctx) irq_enable(); synic_sints_test(dst_vcpu); - atomic_inc(&cpus_comp_count); } static void synic_test_cleanup(void *ctx) { - int vcpu = smp_id(); int i; irq_enable(); for (i = 0; i < HV_SYNIC_SINT_COUNT; i++) { - synic_sint_destroy(vcpu, i); - wrmsr(HV_X64_MSR_SINT0 + i, 0xFF|HV_SYNIC_SINT_MASKED); + synic_sint_destroy(i); } wrmsr(HV_X64_MSR_SCONTROL, 0); wrmsr(HV_X64_MSR_SIMP, 0); wrmsr(HV_X64_MSR_SIEFP, 0); - atomic_inc(&cpus_comp_count); } int main(int ac, char **av) @@ -156,40 +149,25 @@ int main(int ac, char **av) smp_init(); enable_apic(); - synic_prepare_sint_vecs(); - ncpus = cpu_count(); - if (ncpus > MAX_CPUS) { - ncpus = MAX_CPUS; - } + if (ncpus > MAX_CPUS) + report_abort("number cpus exceeds %d", MAX_CPUS); printf("ncpus = %d\n", ncpus); - atomic_set(&cpus_comp_count, 0); - for (i = 0; i < ncpus; i++) { - on_cpu_async(i, synic_test_prepare, (void *)read_cr3()); - } - printf("prepare\n"); - while (atomic_read(&cpus_comp_count) != ncpus) { - pause(); - } + synic_prepare_sint_vecs(); + + printf("prepare\n"); + on_cpus(synic_test_prepare, (void *)read_cr3()); - atomic_set(&cpus_comp_count, 0); for (i = 0; i < ncpus; i++) { printf("test %d -> %d\n", i, ncpus - 1 - i); on_cpu_async(i, synic_test, (void *)(ulong)(ncpus - 1 - i)); } - while (atomic_read(&cpus_comp_count) != ncpus) { + while (cpus_active() > 1) pause(); - } - atomic_set(&cpus_comp_count, 0); - for (i = 0; i < ncpus; i++) { - on_cpu_async(i, synic_test_cleanup, NULL); - } printf("cleanup\n"); - while (atomic_read(&cpus_comp_count) != ncpus) { - pause(); - } + on_cpus(synic_test_cleanup, NULL); ok = true; for (i = 0; i < ncpus; ++i) { diff --git a/tests/kvm-unit-tests/x86/kvmclock_test.c b/tests/kvm-unit-tests/x86/kvmclock_test.c index b90203e0..48a7cdb2 100644 --- a/tests/kvm-unit-tests/x86/kvmclock_test.c +++ b/tests/kvm-unit-tests/x86/kvmclock_test.c @@ -17,7 +17,6 @@ struct test_info { u64 stalls; /* stall count */ long long worst; /* worst warp */ volatile cycle_t last; /* last cycle seen by test */ - atomic_t ncpus; /* number of cpu in the test*/ int check; /* check cycle ? */ }; @@ -78,29 +77,20 @@ static void kvm_clock_test(void *data) if (!((unsigned long)i & 31)) asm volatile("rep; nop"); } - - atomic_dec(&hv_test_info->ncpus); } -static int cycle_test(int ncpus, int check, struct test_info *ti) +static int cycle_test(int check, struct test_info *ti) { - int i; unsigned long long begin, end; begin = rdtsc(); - atomic_set(&ti->ncpus, ncpus); ti->check = check; - for (i = ncpus - 1; i >= 0; i--) - on_cpu_async(i, kvm_clock_test, (void *)ti); - - /* Wait for the end of other vcpu */ - while(atomic_read(&ti->ncpus)) - ; + on_cpus(kvm_clock_test, ti); end = rdtsc(); - printf("Total vcpus: %d\n", ncpus); + printf("Total vcpus: %d\n", cpu_count()); printf("Test loops: %ld\n", loops); if (check == 1) { printf("Total warps: %" PRId64 "\n", ti->warps); @@ -129,9 +119,9 @@ int main(int ac, char **av) ncpus = cpu_count(); if (ncpus > MAX_CPU) - ncpus = MAX_CPU; - for (i = 0; i < ncpus; ++i) - on_cpu(i, kvm_clock_init, (void *)0); + report_abort("number cpus exceeds %d", MAX_CPU); + + on_cpus(kvm_clock_init, NULL); if (ac > 2) { printf("Wallclock test, threshold %ld\n", threshold); @@ -143,26 +133,25 @@ int main(int ac, char **av) printf("Check the stability of raw cycle ...\n"); pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT | PVCLOCK_RAW_CYCLE_BIT); - if (cycle_test(ncpus, 1, &ti[0])) + if (cycle_test(1, &ti[0])) printf("Raw cycle is not stable\n"); else printf("Raw cycle is stable\n"); pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT); printf("Monotonic cycle test:\n"); - nerr += cycle_test(ncpus, 1, &ti[1]); + nerr += cycle_test(1, &ti[1]); printf("Measure the performance of raw cycle ...\n"); pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT | PVCLOCK_RAW_CYCLE_BIT); - cycle_test(ncpus, 0, &ti[2]); + cycle_test(0, &ti[2]); printf("Measure the performance of adjusted cycle ...\n"); pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT); - cycle_test(ncpus, 0, &ti[3]); + cycle_test(0, &ti[3]); - for (i = 0; i < ncpus; ++i) - on_cpu(i, kvm_clock_clear, (void *)0); + on_cpus(kvm_clock_clear, NULL); return nerr > 0 ? 1 : 0; } diff --git a/tests/kvm-unit-tests/x86/msr.c b/tests/kvm-unit-tests/x86/msr.c index ded94249..ffc24b1e 100644 --- a/tests/kvm-unit-tests/x86/msr.c +++ b/tests/kvm-unit-tests/x86/msr.c @@ -6,7 +6,7 @@ struct msr_info { int index; - char *name; + const char *name; struct tc { int valid; unsigned long long value; @@ -78,37 +78,20 @@ static void test_msr_rw(int msr_index, unsigned long long input, unsigned long l { unsigned long long r = 0; int index; - char *sptr; + const char *sptr; if ((index = find_msr_info(msr_index)) != -1) { sptr = msr_info[index].name; } else { - printf("couldn't find name for msr # 0x%x, skipping\n", msr_index); + printf("couldn't find name for msr # %#x, skipping\n", msr_index); return; } wrmsr(msr_index, input); r = rdmsr(msr_index); if (expected != r) { - printf("testing %s: output = 0x%x:0x%x expected = 0x%x:0x%x\n", sptr, + printf("testing %s: output = %#x:%#x expected = %#x:%#x\n", sptr, (u32)(r >> 32), (u32)r, (u32)(expected >> 32), (u32)expected); } - report(sptr, expected == r); -} - -static void test_syscall_lazy_load(void) -{ -#ifdef __x86_64__ - extern void syscall_target(); - u16 cs = read_cs(), ss = read_ss(); - ulong tmp; - - wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SCE); - wrmsr(MSR_LSTAR, (ulong)syscall_target); - wrmsr(MSR_STAR, (uint64_t)cs << 32); - asm volatile("pushf; syscall; syscall_target: popf" : "=c"(tmp) : : "r11"); - write_ss(ss); - // will crash horribly if broken - report("MSR_*STAR eager loading", true); -#endif + report("%s", expected == r, sptr); } int main(int ac, char **av) @@ -124,8 +107,6 @@ int main(int ac, char **av) } } - test_syscall_lazy_load(); - return report_summary(); } diff --git a/tests/kvm-unit-tests/x86/pmu.c b/tests/kvm-unit-tests/x86/pmu.c index c6898004..a0238dcd 100644 --- a/tests/kvm-unit-tests/x86/pmu.c +++ b/tests/kvm-unit-tests/x86/pmu.c @@ -73,7 +73,7 @@ union cpuid10_edx { } edx; struct pmu_event { - char *name; + const char *name; uint32_t unit_sel; int min; int max; diff --git a/tests/kvm-unit-tests/x86/run b/tests/kvm-unit-tests/x86/run deleted file mode 100755 index 867a1ccc..00000000 --- a/tests/kvm-unit-tests/x86/run +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -[ -z "$STANDALONE" ] && source scripts/arch-run.bash - -qemubinarysearch="${QEMU:-qemu-kvm qemu-system-x86_64}" - -for qemucmd in ${qemubinarysearch} -do - unset QEMUFOUND - unset qemu - if ! [ -z "${QEMUFOUND=$(${qemucmd} --help 2>/dev/null | grep "QEMU")}" ] && - ${qemucmd} -device '?' 2>&1 | grep -F -e \"testdev\" -e \"pc-testdev\" > /dev/null; - then - qemu="${qemucmd}" - break - fi -done - -if [ -z "${QEMUFOUND}" ] -then - echo "A QEMU binary was not found, You can set a custom location by using the QEMU= environment variable " - exit 2 -elif [ -z "${qemu}" ] -then - echo "No Qemu test device support found" - exit 2 -fi - -if - ${qemu} -device '?' 2>&1 | grep -F "pci-testdev" > /dev/null; -then - pci_testdev="-device pci-testdev" -else - pci_testdev="" -fi - -if - ${qemu} -device '?' 2>&1 | grep -F "pc-testdev" > /dev/null; -then - pc_testdev="-device pc-testdev -device isa-debug-exit,iobase=0xf4,iosize=0x4" -else - pc_testdev="-device testdev,chardev=testlog -chardev file,id=testlog,path=msr.out" -fi - -command="${qemu} -nodefaults -enable-kvm $pc_testdev -vnc none -serial stdio $pci_testdev $hyperv_testdev" -[ -f "$ENV" ] && command+=" -initrd $ENV" -command+=" -kernel" -command="$(timeout_cmd) $command" -echo ${command} "$@" - -run_qemu ${command} "$@" diff --git a/tests/kvm-unit-tests/x86/tsc.c b/tests/kvm-unit-tests/x86/tsc.c index 62450e71..6dc05af2 100644 --- a/tests/kvm-unit-tests/x86/tsc.c +++ b/tests/kvm-unit-tests/x86/tsc.c @@ -23,7 +23,7 @@ void test_rdtscp(u64 aux) wrmsr(MSR_TSC_AUX, aux); rdtscp(&ecx); - report("Test RDTSCP %d", ecx == aux, aux); + report("Test RDTSCP %" PRIu64, ecx == aux, aux); } int main() diff --git a/tests/kvm-unit-tests/x86/unittests.cfg b/tests/kvm-unit-tests/x86/unittests.cfg deleted file mode 100644 index 54138388..00000000 --- a/tests/kvm-unit-tests/x86/unittests.cfg +++ /dev/null @@ -1,226 +0,0 @@ -############################################################################## -# unittest configuration -# -# [unittest_name] -# file = .flat # Name of the flat file to be used. -# smp = # Number of processors the VM will use -# # during this test. Use $MAX_SMP to use -# # the maximum the host supports. Defaults -# # to one. -# extra_params = -append # Additional parameters used. -# arch = i386|x86_64 # Select one if the test case is -# # specific to only one. -# groups = ... # Used to identify test cases -# # with run_tests -g ... -# # Specify group_name=nodefault -# # to have test not run by -# # default -# accel = kvm|tcg # Optionally specify if test must run with -# # kvm or tcg. If not specified, then kvm will -# # be used when available. -# timeout = # Optionally specify a timeout. -# check = = # check a file for a particular value before running -# # a test. The check line can contain multiple files -# # to check separated by a space but each check -# # parameter needs to be of the form = -############################################################################## - -[apic-split] -file = apic.flat -smp = 2 -extra_params = -cpu qemu64,+x2apic,+tsc-deadline -machine kernel_irqchip=split -arch = x86_64 - -[ioapic-split] -file = ioapic.flat -extra_params = -cpu qemu64 -machine kernel_irqchip=split -arch = x86_64 - -[apic] -file = apic.flat -smp = 2 -extra_params = -cpu qemu64,+x2apic,+tsc-deadline -arch = x86_64 -timeout = 30 - -[ioapic] -file = ioapic.flat -extra_params = -cpu qemu64 -arch = x86_64 - -[smptest] -file = smptest.flat -smp = 2 - -[smptest3] -file = smptest.flat -smp = 3 - -[vmexit_cpuid] -file = vmexit.flat -extra_params = -append 'cpuid' -groups = vmexit - -[vmexit_vmcall] -file = vmexit.flat -extra_params = -append 'vmcall' -groups = vmexit - -[vmexit_mov_from_cr8] -file = vmexit.flat -extra_params = -append 'mov_from_cr8' -groups = vmexit - -[vmexit_mov_to_cr8] -file = vmexit.flat -extra_params = -append 'mov_to_cr8' -groups = vmexit - -[vmexit_inl_pmtimer] -file = vmexit.flat -extra_params = -append 'inl_from_pmtimer' -groups = vmexit - -[vmexit_ipi] -file = vmexit.flat -smp = 2 -extra_params = -append 'ipi' -groups = vmexit - -[vmexit_ipi_halt] -file = vmexit.flat -smp = 2 -extra_params = -append 'ipi_halt' -groups = vmexit - -[vmexit_ple_round_robin] -file = vmexit.flat -extra_params = -append 'ple_round_robin' -groups = vmexit - -[access] -file = access.flat -arch = x86_64 - -[smap] -file = smap.flat -extra_params = -cpu host - -[pku] -file = pku.flat -arch = x86_64 -extra_params = -cpu host - -#[asyncpf] -#file = asyncpf.flat - -[emulator] -file = emulator.flat -arch = x86_64 - -[eventinj] -file = eventinj.flat - -[hypercall] -file = hypercall.flat - -[idt_test] -file = idt_test.flat -arch = x86_64 - -#[init] -#file = init.flat - -[msr] -file = msr.flat - -[pmu] -file = pmu.flat -extra_params = -cpu host -check = /proc/sys/kernel/nmi_watchdog=0 - -[port80] -file = port80.flat - -[realmode] -file = realmode.flat - -[s3] -file = s3.flat - -[sieve] -file = sieve.flat - -[tsc] -file = tsc.flat -extra_params = -cpu kvm64,+rdtscp - -[tsc_adjust] -file = tsc_adjust.flat -extra_params = -cpu host - -[xsave] -file = xsave.flat -arch = x86_64 -extra_params = -cpu host - -[rmap_chain] -file = rmap_chain.flat -arch = x86_64 - -[svm] -file = svm.flat -smp = 2 -extra_params = -cpu qemu64,+svm -arch = x86_64 - -[taskswitch] -file = taskswitch.flat -arch = i386 -groups = tasks - -[taskswitch2] -file = taskswitch2.flat -arch = i386 -groups = tasks - -[kvmclock_test] -file = kvmclock_test.flat -smp = 2 -extra_params = --append "10000000 `date +%s`" - -[pcid] -file = pcid.flat -extra_params = -cpu qemu64,+pcid -arch = x86_64 - -[vmx] -file = vmx.flat -extra_params = -cpu host,+vmx -arch = x86_64 - -[debug] -file = debug.flat -arch = x86_64 - -[hyperv_synic] -file = hyperv_synic.flat -smp = 2 -extra_params = -cpu kvm64,hv_synic -device hyperv-testdev - -[hyperv_stimer] -file = hyperv_stimer.flat -smp = 2 -extra_params = -cpu kvm64,hv_time,hv_synic,hv_stimer -device hyperv-testdev - -[hyperv_clock] -file = hyperv_clock.flat -smp = 2 -extra_params = -cpu kvm64,hv_time - -[intel_iommu] -file = intel-iommu.flat -arch = x86_64 -timeout = 30 -smp = 4 -extra_params = -M q35,kernel-irqchip=split -device intel-iommu,intremap=on,eim=off -device edu diff --git a/tests/kvm-unit-tests/x86/vmexit.c b/tests/kvm-unit-tests/x86/vmexit.c index 5b821b5e..42ab7dbf 100644 --- a/tests/kvm-unit-tests/x86/vmexit.c +++ b/tests/kvm-unit-tests/x86/vmexit.c @@ -387,6 +387,32 @@ static bool pci_io_next(struct test *test) return ret; } +static int has_tscdeadline(void) +{ + uint32_t lvtt; + + if (cpuid(1).c & (1 << 24)) { + lvtt = APIC_LVT_TIMER_TSCDEADLINE | IPI_TEST_VECTOR; + apic_write(APIC_LVTT, lvtt); + return 1; + } else { + return 0; + } +} + +static void tscdeadline_immed(void) +{ + wrmsr(MSR_IA32_TSCDEADLINE, rdtsc()); + asm volatile("nop"); +} + +static void tscdeadline(void) +{ + x = 0; + wrmsr(MSR_IA32_TSCDEADLINE, rdtsc()+3000); + while (x == 0) barrier(); +} + static struct test tests[] = { { cpuid_test, "cpuid", .parallel = 1, }, { vmcall, "vmcall", .parallel = 1, }, @@ -399,6 +425,8 @@ static struct test tests[] = { { inl_nop_kernel, "inl_from_kernel", .parallel = 1 }, { outl_elcr_kernel, "outl_to_kernel", .parallel = 1 }, { mov_dr, "mov_dr", .parallel = 1 }, + { tscdeadline_immed, "tscdeadline_immed", has_tscdeadline, .parallel = 1, }, + { tscdeadline, "tscdeadline", has_tscdeadline, .parallel = 1, }, { self_ipi_sti_nop, "self_ipi_sti_nop", .parallel = 0, }, { self_ipi_sti_hlt, "self_ipi_sti_hlt", .parallel = 0, }, { self_ipi_tpr, "self_ipi_tpr", .parallel = 0, }, @@ -419,7 +447,6 @@ static struct test tests[] = { }; unsigned iterations; -static atomic_t nr_cpus_done; static void run_test(void *_func) { @@ -428,8 +455,6 @@ static void run_test(void *_func) for (i = 0; i < iterations; ++i) func(); - - atomic_inc(&nr_cpus_done); } static bool do_test(struct test *test) @@ -463,11 +488,7 @@ static bool do_test(struct test *test) for (i = 0; i < iterations; ++i) func(); } else { - atomic_set(&nr_cpus_done, 0); - for (i = cpu_count(); i > 0; i--) - on_cpu_async(i-1, run_test, func); - while (atomic_read(&nr_cpus_done) < cpu_count()) - ; + on_cpus(run_test, func); } t2 = rdtsc(); } while ((t2 - t1) < GOAL); @@ -509,8 +530,7 @@ int main(int ac, char **av) nr_cpus = cpu_count(); irq_enable(); - for (i = cpu_count(); i > 0; i--) - on_cpu(i-1, enable_nx, 0); + on_cpus(enable_nx, NULL); fadt = find_acpi_table_addr(FACP_SIGNATURE); pm_tmr_blk = fadt->pm_tmr_blk; @@ -524,7 +544,7 @@ int main(int ac, char **av) membar = pcidev.resource[PCI_TESTDEV_BAR_MEM]; pci_test.memaddr = ioremap(membar, PAGE_SIZE); pci_test.iobar = pcidev.resource[PCI_TESTDEV_BAR_IO]; - printf("pci-testdev at 0x%x membar %lx iobar %x\n", + printf("pci-testdev at %#x membar %lx iobar %x\n", pcidev.bdf, membar, pci_test.iobar); } diff --git a/tests/kvm-unit-tests/x86/vmx.c b/tests/kvm-unit-tests/x86/vmx.c index da5daa86..889df372 100644 --- a/tests/kvm-unit-tests/x86/vmx.c +++ b/tests/kvm-unit-tests/x86/vmx.c @@ -42,9 +42,26 @@ u32 vpid_cnt; void *guest_stack, *guest_syscall_stack; u32 ctrl_pin, ctrl_enter, ctrl_exit, ctrl_cpu[2]; struct regs regs; + struct vmx_test *current; + +#define MAX_TEST_TEARDOWN_STEPS 10 + +struct test_teardown_step { + test_teardown_func func; + void *data; +}; + +static int teardown_count; +static struct test_teardown_step teardown_steps[MAX_TEST_TEARDOWN_STEPS]; + +static test_guest_func v2_guest_main; + u64 hypercall_field; bool launched; +static int matched; +static int guest_finished; +static int in_guest; union vmx_basic basic; union vmx_ctrl_msr ctrl_pin_rev; @@ -62,6 +79,308 @@ extern void *guest_entry; static volatile u32 stage; +static jmp_buf abort_target; + +struct vmcs_field { + u64 mask; + u64 encoding; +}; + +#define MASK(_bits) GENMASK_ULL((_bits) - 1, 0) +#define MASK_NATURAL MASK(sizeof(unsigned long) * 8) + +static struct vmcs_field vmcs_fields[] = { + { MASK(16), VPID }, + { MASK(16), PINV }, + { MASK(16), EPTP_IDX }, + + { MASK(16), GUEST_SEL_ES }, + { MASK(16), GUEST_SEL_CS }, + { MASK(16), GUEST_SEL_SS }, + { MASK(16), GUEST_SEL_DS }, + { MASK(16), GUEST_SEL_FS }, + { MASK(16), GUEST_SEL_GS }, + { MASK(16), GUEST_SEL_LDTR }, + { MASK(16), GUEST_SEL_TR }, + { MASK(16), GUEST_INT_STATUS }, + + { MASK(16), HOST_SEL_ES }, + { MASK(16), HOST_SEL_CS }, + { MASK(16), HOST_SEL_SS }, + { MASK(16), HOST_SEL_DS }, + { MASK(16), HOST_SEL_FS }, + { MASK(16), HOST_SEL_GS }, + { MASK(16), HOST_SEL_TR }, + + { MASK(64), IO_BITMAP_A }, + { MASK(64), IO_BITMAP_B }, + { MASK(64), MSR_BITMAP }, + { MASK(64), EXIT_MSR_ST_ADDR }, + { MASK(64), EXIT_MSR_LD_ADDR }, + { MASK(64), ENTER_MSR_LD_ADDR }, + { MASK(64), VMCS_EXEC_PTR }, + { MASK(64), TSC_OFFSET }, + { MASK(64), APIC_VIRT_ADDR }, + { MASK(64), APIC_ACCS_ADDR }, + { MASK(64), EPTP }, + + { 0 /* read-only */, INFO_PHYS_ADDR }, + + { MASK(64), VMCS_LINK_PTR }, + { MASK(64), GUEST_DEBUGCTL }, + { MASK(64), GUEST_EFER }, + { MASK(64), GUEST_PAT }, + { MASK(64), GUEST_PERF_GLOBAL_CTRL }, + { MASK(64), GUEST_PDPTE }, + + { MASK(64), HOST_PAT }, + { MASK(64), HOST_EFER }, + { MASK(64), HOST_PERF_GLOBAL_CTRL }, + + { MASK(32), PIN_CONTROLS }, + { MASK(32), CPU_EXEC_CTRL0 }, + { MASK(32), EXC_BITMAP }, + { MASK(32), PF_ERROR_MASK }, + { MASK(32), PF_ERROR_MATCH }, + { MASK(32), CR3_TARGET_COUNT }, + { MASK(32), EXI_CONTROLS }, + { MASK(32), EXI_MSR_ST_CNT }, + { MASK(32), EXI_MSR_LD_CNT }, + { MASK(32), ENT_CONTROLS }, + { MASK(32), ENT_MSR_LD_CNT }, + { MASK(32), ENT_INTR_INFO }, + { MASK(32), ENT_INTR_ERROR }, + { MASK(32), ENT_INST_LEN }, + { MASK(32), TPR_THRESHOLD }, + { MASK(32), CPU_EXEC_CTRL1 }, + + { 0 /* read-only */, VMX_INST_ERROR }, + { 0 /* read-only */, EXI_REASON }, + { 0 /* read-only */, EXI_INTR_INFO }, + { 0 /* read-only */, EXI_INTR_ERROR }, + { 0 /* read-only */, IDT_VECT_INFO }, + { 0 /* read-only */, IDT_VECT_ERROR }, + { 0 /* read-only */, EXI_INST_LEN }, + { 0 /* read-only */, EXI_INST_INFO }, + + { MASK(32), GUEST_LIMIT_ES }, + { MASK(32), GUEST_LIMIT_CS }, + { MASK(32), GUEST_LIMIT_SS }, + { MASK(32), GUEST_LIMIT_DS }, + { MASK(32), GUEST_LIMIT_FS }, + { MASK(32), GUEST_LIMIT_GS }, + { MASK(32), GUEST_LIMIT_LDTR }, + { MASK(32), GUEST_LIMIT_TR }, + { MASK(32), GUEST_LIMIT_GDTR }, + { MASK(32), GUEST_LIMIT_IDTR }, + { 0x1d0ff, GUEST_AR_ES }, + { 0x1f0ff, GUEST_AR_CS }, + { 0x1d0ff, GUEST_AR_SS }, + { 0x1d0ff, GUEST_AR_DS }, + { 0x1d0ff, GUEST_AR_FS }, + { 0x1d0ff, GUEST_AR_GS }, + { 0x1d0ff, GUEST_AR_LDTR }, + { 0x1d0ff, GUEST_AR_TR }, + { MASK(32), GUEST_INTR_STATE }, + { MASK(32), GUEST_ACTV_STATE }, + { MASK(32), GUEST_SMBASE }, + { MASK(32), GUEST_SYSENTER_CS }, + { MASK(32), PREEMPT_TIMER_VALUE }, + + { MASK(32), HOST_SYSENTER_CS }, + + { MASK_NATURAL, CR0_MASK }, + { MASK_NATURAL, CR4_MASK }, + { MASK_NATURAL, CR0_READ_SHADOW }, + { MASK_NATURAL, CR4_READ_SHADOW }, + { MASK_NATURAL, CR3_TARGET_0 }, + { MASK_NATURAL, CR3_TARGET_1 }, + { MASK_NATURAL, CR3_TARGET_2 }, + { MASK_NATURAL, CR3_TARGET_3 }, + + { 0 /* read-only */, EXI_QUALIFICATION }, + { 0 /* read-only */, IO_RCX }, + { 0 /* read-only */, IO_RSI }, + { 0 /* read-only */, IO_RDI }, + { 0 /* read-only */, IO_RIP }, + { 0 /* read-only */, GUEST_LINEAR_ADDRESS }, + + { MASK_NATURAL, GUEST_CR0 }, + { MASK_NATURAL, GUEST_CR3 }, + { MASK_NATURAL, GUEST_CR4 }, + { MASK_NATURAL, GUEST_BASE_ES }, + { MASK_NATURAL, GUEST_BASE_CS }, + { MASK_NATURAL, GUEST_BASE_SS }, + { MASK_NATURAL, GUEST_BASE_DS }, + { MASK_NATURAL, GUEST_BASE_FS }, + { MASK_NATURAL, GUEST_BASE_GS }, + { MASK_NATURAL, GUEST_BASE_LDTR }, + { MASK_NATURAL, GUEST_BASE_TR }, + { MASK_NATURAL, GUEST_BASE_GDTR }, + { MASK_NATURAL, GUEST_BASE_IDTR }, + { MASK_NATURAL, GUEST_DR7 }, + { MASK_NATURAL, GUEST_RSP }, + { MASK_NATURAL, GUEST_RIP }, + { MASK_NATURAL, GUEST_RFLAGS }, + { MASK_NATURAL, GUEST_PENDING_DEBUG }, + { MASK_NATURAL, GUEST_SYSENTER_ESP }, + { MASK_NATURAL, GUEST_SYSENTER_EIP }, + + { MASK_NATURAL, HOST_CR0 }, + { MASK_NATURAL, HOST_CR3 }, + { MASK_NATURAL, HOST_CR4 }, + { MASK_NATURAL, HOST_BASE_FS }, + { MASK_NATURAL, HOST_BASE_GS }, + { MASK_NATURAL, HOST_BASE_TR }, + { MASK_NATURAL, HOST_BASE_GDTR }, + { MASK_NATURAL, HOST_BASE_IDTR }, + { MASK_NATURAL, HOST_SYSENTER_ESP }, + { MASK_NATURAL, HOST_SYSENTER_EIP }, + { MASK_NATURAL, HOST_RSP }, + { MASK_NATURAL, HOST_RIP }, +}; + +static inline u64 vmcs_field_value(struct vmcs_field *f, u8 cookie) +{ + u64 value; + + /* Incorporate the cookie and the field encoding into the value. */ + value = cookie; + value |= (f->encoding << 8); + value |= 0xdeadbeefull << 32; + + return value & f->mask; +} + +static void set_vmcs_field(struct vmcs_field *f, u8 cookie) +{ + vmcs_write(f->encoding, vmcs_field_value(f, cookie)); +} + +static bool check_vmcs_field(struct vmcs_field *f, u8 cookie) +{ + u64 expected; + u64 actual; + int ret; + + ret = vmcs_read_checking(f->encoding, &actual); + assert(!(ret & X86_EFLAGS_CF)); + /* Skip VMCS fields that aren't recognized by the CPU */ + if (ret & X86_EFLAGS_ZF) + return true; + + expected = vmcs_field_value(f, cookie); + actual &= f->mask; + + if (expected == actual) + return true; + + printf("FAIL: VMWRITE/VMREAD %lx (expected: %lx, actual: %lx)\n", + f->encoding, (unsigned long) expected, (unsigned long) actual); + + return false; +} + +static void set_all_vmcs_fields(u8 cookie) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vmcs_fields); i++) + set_vmcs_field(&vmcs_fields[i], cookie); +} + +static bool check_all_vmcs_fields(u8 cookie) +{ + bool pass = true; + int i; + + for (i = 0; i < ARRAY_SIZE(vmcs_fields); i++) { + if (!check_vmcs_field(&vmcs_fields[i], cookie)) + pass = false; + } + + return pass; +} + +void test_vmwrite_vmread(void) +{ + struct vmcs *vmcs = alloc_page(); + + memset(vmcs, 0, PAGE_SIZE); + vmcs->revision_id = basic.revision; + assert(!vmcs_clear(vmcs)); + assert(!make_vmcs_current(vmcs)); + + set_all_vmcs_fields(0x42); + report("VMWRITE/VMREAD", check_all_vmcs_fields(0x42)); + + assert(!vmcs_clear(vmcs)); + free_page(vmcs); +} + +void test_vmcs_lifecycle(void) +{ + struct vmcs *vmcs[2] = {}; + int i; + + for (i = 0; i < ARRAY_SIZE(vmcs); i++) { + vmcs[i] = alloc_page(); + memset(vmcs[i], 0, PAGE_SIZE); + vmcs[i]->revision_id = basic.revision; + } + +#define VMPTRLD(_i) do { \ + assert(_i < ARRAY_SIZE(vmcs)); \ + assert(!make_vmcs_current(vmcs[_i])); \ + printf("VMPTRLD VMCS%d\n", (_i)); \ +} while (0) + +#define VMCLEAR(_i) do { \ + assert(_i < ARRAY_SIZE(vmcs)); \ + assert(!vmcs_clear(vmcs[_i])); \ + printf("VMCLEAR VMCS%d\n", (_i)); \ +} while (0) + + VMCLEAR(0); + VMPTRLD(0); + set_all_vmcs_fields(0); + report("current:VMCS0 active:[VMCS0]", check_all_vmcs_fields(0)); + + VMCLEAR(0); + VMPTRLD(0); + report("current:VMCS0 active:[VMCS0]", check_all_vmcs_fields(0)); + + VMCLEAR(1); + report("current:VMCS0 active:[VMCS0]", check_all_vmcs_fields(0)); + + VMPTRLD(1); + set_all_vmcs_fields(1); + report("current:VMCS1 active:[VMCS0,VCMS1]", check_all_vmcs_fields(1)); + + VMPTRLD(0); + report("current:VMCS0 active:[VMCS0,VCMS1]", check_all_vmcs_fields(0)); + VMPTRLD(1); + report("current:VMCS1 active:[VMCS0,VCMS1]", check_all_vmcs_fields(1)); + VMPTRLD(1); + report("current:VMCS1 active:[VMCS0,VCMS1]", check_all_vmcs_fields(1)); + + VMCLEAR(0); + report("current:VMCS1 active:[VCMS1]", check_all_vmcs_fields(1)); + + /* VMPTRLD should not erase VMWRITEs to the current VMCS */ + set_all_vmcs_fields(2); + VMPTRLD(1); + report("current:VMCS1 active:[VCMS1]", check_all_vmcs_fields(2)); + + for (i = 0; i < ARRAY_SIZE(vmcs); i++) { + VMCLEAR(i); + free_page(vmcs[i]); + } + +#undef VMPTRLD +#undef VMCLEAR +} + void vmx_set_test_stage(u32 s) { barrier(); @@ -86,16 +405,6 @@ void vmx_inc_test_stage(void) barrier(); } -static int make_vmcs_current(struct vmcs *vmcs) -{ - bool ret; - u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; - - asm volatile ("push %1; popf; vmptrld %2; setbe %0" - : "=q" (ret) : "q" (rflags), "m" (vmcs) : "cc"); - return ret; -} - /* entry_sysenter */ asm( ".align 4, 0x90\n\t" @@ -115,23 +424,73 @@ static void __attribute__((__used__)) syscall_handler(u64 syscall_no) current->syscall_handler(syscall_no); } -static inline int vmx_on() -{ - bool ret; - u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; - asm volatile ("push %1; popf; vmxon %2; setbe %0\n\t" - : "=q" (ret) : "q" (rflags), "m" (vmxon_region) : "cc"); - return ret; -} +static const char * const exit_reason_descriptions[] = { + [VMX_EXC_NMI] = "VMX_EXC_NMI", + [VMX_EXTINT] = "VMX_EXTINT", + [VMX_TRIPLE_FAULT] = "VMX_TRIPLE_FAULT", + [VMX_INIT] = "VMX_INIT", + [VMX_SIPI] = "VMX_SIPI", + [VMX_SMI_IO] = "VMX_SMI_IO", + [VMX_SMI_OTHER] = "VMX_SMI_OTHER", + [VMX_INTR_WINDOW] = "VMX_INTR_WINDOW", + [VMX_NMI_WINDOW] = "VMX_NMI_WINDOW", + [VMX_TASK_SWITCH] = "VMX_TASK_SWITCH", + [VMX_CPUID] = "VMX_CPUID", + [VMX_GETSEC] = "VMX_GETSEC", + [VMX_HLT] = "VMX_HLT", + [VMX_INVD] = "VMX_INVD", + [VMX_INVLPG] = "VMX_INVLPG", + [VMX_RDPMC] = "VMX_RDPMC", + [VMX_RDTSC] = "VMX_RDTSC", + [VMX_RSM] = "VMX_RSM", + [VMX_VMCALL] = "VMX_VMCALL", + [VMX_VMCLEAR] = "VMX_VMCLEAR", + [VMX_VMLAUNCH] = "VMX_VMLAUNCH", + [VMX_VMPTRLD] = "VMX_VMPTRLD", + [VMX_VMPTRST] = "VMX_VMPTRST", + [VMX_VMREAD] = "VMX_VMREAD", + [VMX_VMRESUME] = "VMX_VMRESUME", + [VMX_VMWRITE] = "VMX_VMWRITE", + [VMX_VMXOFF] = "VMX_VMXOFF", + [VMX_VMXON] = "VMX_VMXON", + [VMX_CR] = "VMX_CR", + [VMX_DR] = "VMX_DR", + [VMX_IO] = "VMX_IO", + [VMX_RDMSR] = "VMX_RDMSR", + [VMX_WRMSR] = "VMX_WRMSR", + [VMX_FAIL_STATE] = "VMX_FAIL_STATE", + [VMX_FAIL_MSR] = "VMX_FAIL_MSR", + [VMX_MWAIT] = "VMX_MWAIT", + [VMX_MTF] = "VMX_MTF", + [VMX_MONITOR] = "VMX_MONITOR", + [VMX_PAUSE] = "VMX_PAUSE", + [VMX_FAIL_MCHECK] = "VMX_FAIL_MCHECK", + [VMX_TPR_THRESHOLD] = "VMX_TPR_THRESHOLD", + [VMX_APIC_ACCESS] = "VMX_APIC_ACCESS", + [VMX_GDTR_IDTR] = "VMX_GDTR_IDTR", + [VMX_LDTR_TR] = "VMX_LDTR_TR", + [VMX_EPT_VIOLATION] = "VMX_EPT_VIOLATION", + [VMX_EPT_MISCONFIG] = "VMX_EPT_MISCONFIG", + [VMX_INVEPT] = "VMX_INVEPT", + [VMX_PREEMPT] = "VMX_PREEMPT", + [VMX_INVVPID] = "VMX_INVVPID", + [VMX_WBINVD] = "VMX_WBINVD", + [VMX_XSETBV] = "VMX_XSETBV", + [VMX_APIC_WRITE] = "VMX_APIC_WRITE", + [VMX_RDRAND] = "VMX_RDRAND", + [VMX_INVPCID] = "VMX_INVPCID", + [VMX_VMFUNC] = "VMX_VMFUNC", + [VMX_RDSEED] = "VMX_RDSEED", + [VMX_PML_FULL] = "VMX_PML_FULL", + [VMX_XSAVES] = "VMX_XSAVES", + [VMX_XRSTORS] = "VMX_XRSTORS", +}; -static inline int vmx_off() +const char *exit_reason_description(u64 reason) { - bool ret; - u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; - - asm volatile("push %1; popf; vmxoff; setbe %0\n\t" - : "=q"(ret) : "q" (rflags) : "cc"); - return ret; + if (reason >= ARRAY_SIZE(exit_reason_descriptions)) + return "(unknown)"; + return exit_reason_descriptions[reason] ? : "(unused)"; } void print_vmexit_info() @@ -143,16 +502,16 @@ void print_vmexit_info() guest_rsp = vmcs_read(GUEST_RSP); printf("VMEXIT info:\n"); printf("\tvmexit reason = %ld\n", reason); - printf("\texit qualification = 0x%lx\n", exit_qual); + printf("\texit qualification = %#lx\n", exit_qual); printf("\tBit 31 of reason = %lx\n", (vmcs_read(EXI_REASON) >> 31) & 1); - printf("\tguest_rip = 0x%lx\n", guest_rip); - printf("\tRAX=0x%lx RBX=0x%lx RCX=0x%lx RDX=0x%lx\n", + printf("\tguest_rip = %#lx\n", guest_rip); + printf("\tRAX=%#lx RBX=%#lx RCX=%#lx RDX=%#lx\n", regs.rax, regs.rbx, regs.rcx, regs.rdx); - printf("\tRSP=0x%lx RBP=0x%lx RSI=0x%lx RDI=0x%lx\n", + printf("\tRSP=%#lx RBP=%#lx RSI=%#lx RDI=%#lx\n", guest_rsp, regs.rbp, regs.rsi, regs.rdi); - printf("\tR8 =0x%lx R9 =0x%lx R10=0x%lx R11=0x%lx\n", + printf("\tR8 =%#lx R9 =%#lx R10=%#lx R11=%#lx\n", regs.r8, regs.r9, regs.r10, regs.r11); - printf("\tR12=0x%lx R13=0x%lx R14=0x%lx R15=0x%lx\n", + printf("\tR12=%#lx R13=%#lx R14=%#lx R15=%#lx\n", regs.r12, regs.r13, regs.r14, regs.r15); } @@ -175,7 +534,7 @@ print_vmentry_failure_info(struct vmentry_failure *failure) { u64 reason = vmcs_read(EXI_REASON); u64 qual = vmcs_read(EXI_QUALIFICATION); - printf("Non-early %s failure (reason=0x%lx, qual=0x%lx): ", + printf("Non-early %s failure (reason=%#lx, qual=%#lx): ", failure->instr, reason, qual); switch (reason & 0xff) { @@ -201,6 +560,42 @@ print_vmentry_failure_info(struct vmentry_failure *failure) { } } +/* + * VMCLEAR should ensures all VMCS state is flushed to the VMCS + * region in memory. + */ +static void test_vmclear_flushing(void) +{ + struct vmcs *vmcs[3] = {}; + int i; + + for (i = 0; i < ARRAY_SIZE(vmcs); i++) { + vmcs[i] = alloc_page(); + memset(vmcs[i], 0, PAGE_SIZE); + } + + vmcs[0]->revision_id = basic.revision; + assert(!vmcs_clear(vmcs[0])); + assert(!make_vmcs_current(vmcs[0])); + set_all_vmcs_fields(0x86); + + assert(!vmcs_clear(vmcs[0])); + memcpy(vmcs[1], vmcs[0], basic.size); + assert(!make_vmcs_current(vmcs[1])); + report("test vmclear flush (current VMCS)", check_all_vmcs_fields(0x86)); + + set_all_vmcs_fields(0x87); + assert(!make_vmcs_current(vmcs[0])); + assert(!vmcs_clear(vmcs[1])); + memcpy(vmcs[2], vmcs[1], basic.size); + assert(!make_vmcs_current(vmcs[2])); + report("test vmclear flush (!current VMCS)", check_all_vmcs_fields(0x87)); + + for (i = 0; i < ARRAY_SIZE(vmcs); i++) { + assert(!vmcs_clear(vmcs[i])); + free_page(vmcs[i]); + } +} static void test_vmclear(void) { @@ -233,19 +628,15 @@ static void test_vmclear(void) /* Valid VMCS */ report("test vmclear with valid vmcs region", vmcs_clear(vmcs_root) == 0); -} - -static void test_vmxoff(void) -{ - int ret; - - ret = vmx_off(); - report("test vmxoff", !ret); + test_vmclear_flushing(); } static void __attribute__((__used__)) guest_main(void) { - current->guest_main(); + if (current->v2) + v2_guest_main(); + else + current->guest_main(); } /* guest_entry */ @@ -314,6 +705,9 @@ void install_ept_entry(unsigned long *pml4, unsigned long *pt = pml4; unsigned offset; + /* EPT only uses 48 bits of GPA. */ + assert(guest_addr < (1ul << 48)); + for (level = EPT_PAGE_LEVEL; level > pte_level; --level) { offset = (guest_addr >> EPT_LEVEL_SHIFT(level)) & EPT_PGDIR_MASK; @@ -396,31 +790,135 @@ void setup_ept_range(unsigned long *pml4, unsigned long start, /* get_ept_pte : Get the PTE of a given level in EPT, @level == 1 means get the latest level*/ -unsigned long get_ept_pte(unsigned long *pml4, - unsigned long guest_addr, int level) +bool get_ept_pte(unsigned long *pml4, unsigned long guest_addr, int level, + unsigned long *pte) { int l; - unsigned long *pt = pml4, pte; + unsigned long *pt = pml4, iter_pte; unsigned offset; - if (level < 1 || level > 3) - return -1; + assert(level >= 1 && level <= 4); + for (l = EPT_PAGE_LEVEL; ; --l) { offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; - pte = pt[offset]; - if (!(pte & (EPT_PRESENT))) - return 0; + iter_pte = pt[offset]; if (l == level) break; - if (l < 4 && (pte & EPT_LARGE_PAGE)) - return pte; - pt = (unsigned long *)(pte & EPT_ADDR_MASK); + if (l < 4 && (iter_pte & EPT_LARGE_PAGE)) + return false; + if (!(iter_pte & (EPT_PRESENT))) + return false; + pt = (unsigned long *)(iter_pte & EPT_ADDR_MASK); } offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; - pte = pt[offset]; - return pte; + if (pte) + *pte = pt[offset]; + return true; } +static void clear_ept_ad_pte(unsigned long *pml4, unsigned long guest_addr) +{ + int l; + unsigned long *pt = pml4; + u64 pte; + unsigned offset; + + for (l = EPT_PAGE_LEVEL; ; --l) { + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + pt[offset] &= ~(EPT_ACCESS_FLAG|EPT_DIRTY_FLAG); + pte = pt[offset]; + if (l == 1 || (l < 4 && (pte & EPT_LARGE_PAGE))) + break; + pt = (unsigned long *)(pte & EPT_ADDR_MASK); + } +} + +/* clear_ept_ad : Clear EPT A/D bits for the page table walk and the + final GPA of a guest address. */ +void clear_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr) +{ + int l; + unsigned long *pt = (unsigned long *)guest_cr3, gpa; + u64 pte, offset_in_page; + unsigned offset; + + for (l = EPT_PAGE_LEVEL; ; --l) { + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + + clear_ept_ad_pte(pml4, (u64) &pt[offset]); + pte = pt[offset]; + if (l == 1 || (l < 4 && (pte & PT_PAGE_SIZE_MASK))) + break; + if (!(pte & PT_PRESENT_MASK)) + return; + pt = (unsigned long *)(pte & PT_ADDR_MASK); + } + + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + offset_in_page = guest_addr & ((1 << EPT_LEVEL_SHIFT(l)) - 1); + gpa = (pt[offset] & PT_ADDR_MASK) | (guest_addr & offset_in_page); + clear_ept_ad_pte(pml4, gpa); +} + +/* check_ept_ad : Check the content of EPT A/D bits for the page table + walk and the final GPA of a guest address. */ +void check_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr, int expected_gpa_ad, + int expected_pt_ad) +{ + int l; + unsigned long *pt = (unsigned long *)guest_cr3, gpa; + u64 ept_pte, pte, offset_in_page; + unsigned offset; + bool bad_pt_ad = false; + + for (l = EPT_PAGE_LEVEL; ; --l) { + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + + if (!get_ept_pte(pml4, (u64) &pt[offset], 1, &ept_pte)) { + printf("EPT - guest level %d page table is not mapped.\n", l); + return; + } + + if (!bad_pt_ad) { + bad_pt_ad |= (ept_pte & (EPT_ACCESS_FLAG|EPT_DIRTY_FLAG)) != expected_pt_ad; + if (bad_pt_ad) + report("EPT - guest level %d page table A=%d/D=%d", + false, l, + !!(expected_pt_ad & EPT_ACCESS_FLAG), + !!(expected_pt_ad & EPT_DIRTY_FLAG)); + } + + pte = pt[offset]; + if (l == 1 || (l < 4 && (pte & PT_PAGE_SIZE_MASK))) + break; + if (!(pte & PT_PRESENT_MASK)) + return; + pt = (unsigned long *)(pte & PT_ADDR_MASK); + } + + if (!bad_pt_ad) + report("EPT - guest page table structures A=%d/D=%d", + true, + !!(expected_pt_ad & EPT_ACCESS_FLAG), + !!(expected_pt_ad & EPT_DIRTY_FLAG)); + + offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; + offset_in_page = guest_addr & ((1 << EPT_LEVEL_SHIFT(l)) - 1); + gpa = (pt[offset] & PT_ADDR_MASK) | (guest_addr & offset_in_page); + + if (!get_ept_pte(pml4, gpa, 1, &ept_pte)) { + report("EPT - guest physical address is not mapped", false); + return; + } + report("EPT - guest physical address A=%d/D=%d", + (ept_pte & (EPT_ACCESS_FLAG|EPT_DIRTY_FLAG)) == expected_gpa_ad, + !!(expected_gpa_ad & EPT_ACCESS_FLAG), + !!(expected_gpa_ad & EPT_DIRTY_FLAG)); +} + + void ept_sync(int type, u64 eptp) { switch (type) { @@ -441,34 +939,62 @@ void ept_sync(int type, u64 eptp) } } -int set_ept_pte(unsigned long *pml4, unsigned long guest_addr, - int level, u64 pte_val) +void set_ept_pte(unsigned long *pml4, unsigned long guest_addr, + int level, u64 pte_val) { int l; unsigned long *pt = pml4; unsigned offset; - if (level < 1 || level > 3) - return -1; + assert(level >= 1 && level <= 4); + for (l = EPT_PAGE_LEVEL; ; --l) { offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; if (l == level) break; - if (!(pt[offset] & (EPT_PRESENT))) - return -1; + assert(pt[offset] & EPT_PRESENT); pt = (unsigned long *)(pt[offset] & EPT_ADDR_MASK); } offset = (guest_addr >> EPT_LEVEL_SHIFT(l)) & EPT_PGDIR_MASK; pt[offset] = pte_val; - return 0; +} + +bool ept_2m_supported(void) +{ + return ept_vpid.val & EPT_CAP_2M_PAGE; +} + +bool ept_1g_supported(void) +{ + return ept_vpid.val & EPT_CAP_1G_PAGE; +} + +bool ept_huge_pages_supported(int level) +{ + if (level == 2) + return ept_2m_supported(); + else if (level == 3) + return ept_1g_supported(); + else + return false; +} + +bool ept_execute_only_supported(void) +{ + return ept_vpid.val & EPT_CAP_WT; +} + +bool ept_ad_bits_supported(void) +{ + return ept_vpid.val & EPT_CAP_AD_FLAG; } void vpid_sync(int type, u16 vpid) { switch(type) { - case INVVPID_SINGLE: - if (ept_vpid.val & VPID_CAP_INVVPID_SINGLE) { - invvpid(INVVPID_SINGLE, vpid, 0); + case INVVPID_CONTEXT_GLOBAL: + if (ept_vpid.val & VPID_CAP_INVVPID_CXTGLB) { + invvpid(INVVPID_CONTEXT_GLOBAL, vpid, 0); break; } case INVVPID_ALL: @@ -799,9 +1325,12 @@ static void test_vmptrld(void) make_vmcs_current(tmp_root) == 1); /* Pass VMXON region */ + make_vmcs_current(vmcs); tmp_root = (struct vmcs *)vmxon_region; report("test vmptrld with vmxon region", make_vmcs_current(tmp_root) == 1); + report("test vmptrld with vmxon region vm-instruction error", + vmcs_read(VMX_INST_ERROR) == VMXERR_VMPTRLD_VMXON_POINTER); report("test vmptrld with valid vmcs region", make_vmcs_current(vmcs) == 0); } @@ -866,7 +1395,7 @@ static void test_vmx_caps(void) ok = ctrl.clr == true_ctrl.clr; ok = ok && ctrl.set == (true_ctrl.set | default1); } - report(vmx_ctl_msr[n].name, ok); + report("%s", ok, vmx_ctl_msr[n].name); } fixed0 = rdmsr(MSR_IA32_VMX_CR0_FIXED0); @@ -886,7 +1415,7 @@ static void test_vmx_caps(void) val = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP); report("MSR_IA32_VMX_EPT_VPID_CAP", - (val & 0xfffff07ef9eebebeUll) == 0); + (val & 0xfffff07ef98cbebeUll) == 0); } /* This function can only be called in guest */ @@ -918,12 +1447,51 @@ static int handle_hypercall() switch (hypercall_no) { case HYPERCALL_VMEXIT: return VMX_TEST_VMEXIT; + case HYPERCALL_VMABORT: + return VMX_TEST_VMABORT; + case HYPERCALL_VMSKIP: + return VMX_TEST_VMSKIP; default: printf("ERROR : Invalid hypercall number : %ld\n", hypercall_no); } return VMX_TEST_EXIT; } +static void continue_abort(void) +{ + assert(!in_guest); + printf("Host was here when guest aborted:\n"); + dump_stack(); + longjmp(abort_target, 1); + abort(); +} + +void __abort_test(void) +{ + if (in_guest) + hypercall(HYPERCALL_VMABORT); + else + longjmp(abort_target, 1); + abort(); +} + +static void continue_skip(void) +{ + assert(!in_guest); + longjmp(abort_target, 1); + abort(); +} + +void test_skip(const char *msg) +{ + printf("%s skipping test: %s\n", in_guest ? "Guest" : "Host", msg); + if (in_guest) + hypercall(HYPERCALL_VMABORT); + else + longjmp(abort_target, 1); + abort(); +} + static int exit_handler() { int ret; @@ -954,43 +1522,56 @@ entry_failure_handler(struct vmentry_failure *failure) return VMX_TEST_EXIT; } +/* + * Tries to enter the guest. Returns true iff entry succeeded. Otherwise, + * populates @failure. + */ +static bool vmx_enter_guest(struct vmentry_failure *failure) +{ + failure->early = 0; + + in_guest = 1; + asm volatile ( + "mov %[HOST_RSP], %%rdi\n\t" + "vmwrite %%rsp, %%rdi\n\t" + LOAD_GPR_C + "cmpb $0, %[launched]\n\t" + "jne 1f\n\t" + "vmlaunch\n\t" + "jmp 2f\n\t" + "1: " + "vmresume\n\t" + "2: " + SAVE_GPR_C + "pushf\n\t" + "pop %%rdi\n\t" + "mov %%rdi, %[failure_flags]\n\t" + "movl $1, %[failure_flags]\n\t" + "jmp 3f\n\t" + "vmx_return:\n\t" + SAVE_GPR_C + "3: \n\t" + : [failure_early]"+m"(failure->early), + [failure_flags]"=m"(failure->flags) + : [launched]"m"(launched), [HOST_RSP]"i"(HOST_RSP) + : "rdi", "memory", "cc" + ); + in_guest = 0; + + failure->vmlaunch = !launched; + failure->instr = launched ? "vmresume" : "vmlaunch"; + + return !failure->early && !(vmcs_read(EXI_REASON) & VMX_ENTRY_FAILURE); +} + static int vmx_run() { - unsigned long host_rflags; - while (1) { u32 ret; - u32 fail = 0; bool entered; struct vmentry_failure failure; - asm volatile ( - "mov %[HOST_RSP], %%rdi\n\t" - "vmwrite %%rsp, %%rdi\n\t" - LOAD_GPR_C - "cmpb $0, %[launched]\n\t" - "jne 1f\n\t" - "vmlaunch\n\t" - "jmp 2f\n\t" - "1: " - "vmresume\n\t" - "2: " - SAVE_GPR_C - "pushf\n\t" - "pop %%rdi\n\t" - "mov %%rdi, %[host_rflags]\n\t" - "movl $1, %[fail]\n\t" - "jmp 3f\n\t" - "vmx_return:\n\t" - SAVE_GPR_C - "3: \n\t" - : [fail]"+m"(fail), [host_rflags]"=m"(host_rflags) - : [launched]"m"(launched), [HOST_RSP]"i"(HOST_RSP) - : "rdi", "memory", "cc" - - ); - - entered = !fail && !(vmcs_read(EXI_REASON) & VMX_ENTRY_FAILURE); + entered = vmx_enter_guest(&failure); if (entered) { /* @@ -1000,10 +1581,6 @@ static int vmx_run() launched = 1; ret = exit_handler(); } else { - failure.flags = host_rflags; - failure.vmlaunch = !launched; - failure.instr = launched ? "vmresume" : "vmlaunch"; - failure.early = fail; ret = entry_failure_handler(&failure); } @@ -1011,6 +1588,7 @@ static int vmx_run() case VMX_TEST_RESUME: continue; case VMX_TEST_VMEXIT: + guest_finished = 1; return 0; case VMX_TEST_EXIT: break; @@ -1029,26 +1607,67 @@ static int vmx_run() } } +static void run_teardown_step(struct test_teardown_step *step) +{ + step->func(step->data); +} + static int test_run(struct vmx_test *test) { + int r; + + /* Validate V2 interface. */ + if (test->v2) { + int ret = 0; + if (test->init || test->guest_main || test->exit_handler || + test->syscall_handler) { + report("V2 test cannot specify V1 callbacks.", 0); + ret = 1; + } + if (ret) + return ret; + } + if (test->name == NULL) test->name = "(no name)"; if (vmx_on()) { printf("%s : vmxon failed.\n", __func__); return 1; } + init_vmcs(&(test->vmcs)); /* Directly call test->init is ok here, init_vmcs has done vmcs init, vmclear and vmptrld*/ if (test->init && test->init(test->vmcs) != VMX_TEST_START) goto out; + teardown_count = 0; + v2_guest_main = NULL; test->exits = 0; current = test; regs = test->guest_regs; vmcs_write(GUEST_RFLAGS, regs.rflags | 0x2); launched = 0; + guest_finished = 0; printf("\nTest suite: %s\n", test->name); - vmx_run(); + + r = setjmp(abort_target); + if (r) { + assert(!in_guest); + goto out; + } + + + if (test->v2) + test->v2(); + else + vmx_run(); + + while (teardown_count > 0) + run_teardown_step(&teardown_steps[--teardown_count]); + + if (launched && !guest_finished) + report("Guest didn't run to completion.", 0); + out: if (vmx_off()) { printf("%s : vmxoff failed.\n", __func__); @@ -1057,9 +1676,116 @@ out: return 0; } +/* + * Add a teardown step. Executed after the test's main function returns. + * Teardown steps executed in reverse order. + */ +void test_add_teardown(test_teardown_func func, void *data) +{ + struct test_teardown_step *step; + + TEST_ASSERT_MSG(teardown_count < MAX_TEST_TEARDOWN_STEPS, + "There are already %d teardown steps.", + teardown_count); + step = &teardown_steps[teardown_count++]; + step->func = func; + step->data = data; +} + +/* + * Set the target of the first enter_guest call. Can only be called once per + * test. Must be called before first enter_guest call. + */ +void test_set_guest(test_guest_func func) +{ + assert(current->v2); + TEST_ASSERT_MSG(!v2_guest_main, "Already set guest func."); + v2_guest_main = func; +} + +/* + * Enters the guest (or launches it for the first time). Error to call once the + * guest has returned (i.e., run past the end of its guest() function). Also + * aborts if guest entry fails. + */ +void enter_guest(void) +{ + struct vmentry_failure failure; + + TEST_ASSERT_MSG(v2_guest_main, + "Never called test_set_guest_func!"); + + TEST_ASSERT_MSG(!guest_finished, + "Called enter_guest() after guest returned."); + + if (!vmx_enter_guest(&failure)) { + print_vmentry_failure_info(&failure); + abort(); + } + + launched = 1; + + if (is_hypercall()) { + int ret; + + ret = handle_hypercall(); + switch (ret) { + case VMX_TEST_VMEXIT: + guest_finished = 1; + break; + case VMX_TEST_VMABORT: + continue_abort(); + break; + case VMX_TEST_VMSKIP: + continue_skip(); + break; + default: + printf("ERROR : Invalid handle_hypercall return %d.\n", + ret); + abort(); + } + } +} + extern struct vmx_test vmx_tests[]; -int main(void) +static bool +test_wanted(const char *name, const char *filters[], int filter_count) +{ + int i; + bool positive = false; + bool match = false; + char clean_name[strlen(name) + 1]; + char *c; + const char *n; + + /* Replace spaces with underscores. */ + n = name; + c = &clean_name[0]; + do *c++ = (*n == ' ') ? '_' : *n; + while (*n++); + + for (i = 0; i < filter_count; i++) { + const char *filter = filters[i]; + + if (filter[0] == '-') { + if (simple_glob(clean_name, filter + 1)) + return false; + } else { + positive = true; + match |= simple_glob(clean_name, filter); + } + } + + if (!positive || match) { + matched++; + return true; + } else { + return false; + } +} + +int main(int argc, const char *argv[]) { int i = 0; @@ -1067,31 +1793,59 @@ int main(void) setup_idt(); hypercall_field = 0; + argv++; + argc--; + if (!(cpuid(1).c & (1 << 5))) { printf("WARNING: vmx not supported, add '-cpu host'\n"); goto exit; } init_vmx(); - if (test_vmx_feature_control() != 0) - goto exit; - /* Set basic test ctxt the same as "null" */ - current = &vmx_tests[0]; - if (test_vmxon() != 0) - goto exit; - test_vmptrld(); - test_vmclear(); - test_vmptrst(); - init_vmcs(&vmcs_root); - if (vmx_run()) { - report("test vmlaunch", 0); - goto exit; + if (test_wanted("test_vmx_feature_control", argv, argc)) { + /* Sets MSR_IA32_FEATURE_CONTROL to 0x5 */ + if (test_vmx_feature_control() != 0) + goto exit; + } else { + if ((rdmsr(MSR_IA32_FEATURE_CONTROL) & 0x5) != 0x5) + wrmsr(MSR_IA32_FEATURE_CONTROL, 0x5); } - test_vmxoff(); - test_vmx_caps(); - while (vmx_tests[++i].name != NULL) + if (test_wanted("test_vmxon", argv, argc)) { + /* Enables VMX */ + if (test_vmxon() != 0) + goto exit; + } else { + if (vmx_on()) { + report("vmxon", 0); + goto exit; + } + } + + if (test_wanted("test_vmptrld", argv, argc)) + test_vmptrld(); + if (test_wanted("test_vmclear", argv, argc)) + test_vmclear(); + if (test_wanted("test_vmptrst", argv, argc)) + test_vmptrst(); + if (test_wanted("test_vmwrite_vmread", argv, argc)) + test_vmwrite_vmread(); + if (test_wanted("test_vmcs_lifecycle", argv, argc)) + test_vmcs_lifecycle(); + if (test_wanted("test_vmx_caps", argv, argc)) + test_vmx_caps(); + + /* Balance vmxon from test_vmxon. */ + vmx_off(); + + for (; vmx_tests[i].name != NULL; i++) { + if (!test_wanted(vmx_tests[i].name, argv, argc)) + continue; if (test_run(&vmx_tests[i])) goto exit; + } + + if (!matched) + report("command line didn't match any tests!", matched); exit: return report_summary(); diff --git a/tests/kvm-unit-tests/x86/vmx.h b/tests/kvm-unit-tests/x86/vmx.h index a2bacd34..f32dffe1 100644 --- a/tests/kvm-unit-tests/x86/vmx.h +++ b/tests/kvm-unit-tests/x86/vmx.h @@ -5,6 +5,7 @@ #include "processor.h" #include "bitops.h" #include "asm/page.h" +#include "asm/io.h" struct vmcs { u32 revision_id; /* vmcs revision identifier */ @@ -13,6 +14,11 @@ struct vmcs { char data[0]; }; +struct invvpid_operand { + u64 vpid; + u64 gla; +}; + struct regs { u64 rax; u64 rcx; @@ -54,6 +60,8 @@ struct vmx_test { int (*entry_failure_handler)(struct vmentry_failure *failure); struct vmcs *vmcs; int exits; + /* Alternative test interface. */ + void (*v2)(void); }; union vmx_basic { @@ -108,6 +116,7 @@ enum Encoding { GUEST_SEL_LDTR = 0x080cul, GUEST_SEL_TR = 0x080eul, GUEST_INT_STATUS = 0x0810ul, + GUEST_PML_INDEX = 0x0812ul, /* 16-Bit Host State Fields */ HOST_SEL_ES = 0x0c00ul, @@ -132,6 +141,9 @@ enum Encoding { APIC_ACCS_ADDR = 0x2014ul, EPTP = 0x201aul, EPTP_HI = 0x201bul, + PMLADDR = 0x200eul, + PMLADDR_HI = 0x200ful, + /* 64-Bit Readonly Data Field */ INFO_PHYS_ADDR = 0x2400ul, @@ -317,7 +329,15 @@ enum Reason { VMX_PREEMPT = 52, VMX_INVVPID = 53, VMX_WBINVD = 54, - VMX_XSETBV = 55 + VMX_XSETBV = 55, + VMX_APIC_WRITE = 56, + VMX_RDRAND = 57, + VMX_INVPCID = 58, + VMX_VMFUNC = 59, + VMX_RDSEED = 61, + VMX_PML_FULL = 62, + VMX_XSAVES = 63, + VMX_XRSTORS = 64, }; enum Ctrl_exi { @@ -375,6 +395,7 @@ enum Ctrl1 { CPU_URG = 1ul << 7, CPU_WBINVD = 1ul << 6, CPU_RDRAND = 1ul << 11, + CPU_PML = 1ul << 17, }; enum Intr_type { @@ -396,6 +417,37 @@ enum Intr_type { #define INTR_INFO_INTR_TYPE_SHIFT 8 +/* + * VM-instruction error numbers + */ +enum vm_instruction_error_number { + VMXERR_VMCALL_IN_VMX_ROOT_OPERATION = 1, + VMXERR_VMCLEAR_INVALID_ADDRESS = 2, + VMXERR_VMCLEAR_VMXON_POINTER = 3, + VMXERR_VMLAUNCH_NONCLEAR_VMCS = 4, + VMXERR_VMRESUME_NONLAUNCHED_VMCS = 5, + VMXERR_VMRESUME_AFTER_VMXOFF = 6, + VMXERR_ENTRY_INVALID_CONTROL_FIELD = 7, + VMXERR_ENTRY_INVALID_HOST_STATE_FIELD = 8, + VMXERR_VMPTRLD_INVALID_ADDRESS = 9, + VMXERR_VMPTRLD_VMXON_POINTER = 10, + VMXERR_VMPTRLD_INCORRECT_VMCS_REVISION_ID = 11, + VMXERR_UNSUPPORTED_VMCS_COMPONENT = 12, + VMXERR_VMWRITE_READ_ONLY_VMCS_COMPONENT = 13, + VMXERR_VMXON_IN_VMX_ROOT_OPERATION = 15, + VMXERR_ENTRY_INVALID_EXECUTIVE_VMCS_POINTER = 16, + VMXERR_ENTRY_NONLAUNCHED_EXECUTIVE_VMCS = 17, + VMXERR_ENTRY_EXECUTIVE_VMCS_POINTER_NOT_VMXON_POINTER = 18, + VMXERR_VMCALL_NONCLEAR_VMCS = 19, + VMXERR_VMCALL_INVALID_VM_EXIT_CONTROL_FIELDS = 20, + VMXERR_VMCALL_INCORRECT_MSEG_REVISION_ID = 22, + VMXERR_VMXOFF_UNDER_DUAL_MONITOR_TREATMENT_OF_SMIS_AND_SMM = 23, + VMXERR_VMCALL_INVALID_SMM_MONITOR_FEATURES = 24, + VMXERR_ENTRY_INVALID_VM_EXECUTION_CONTROL_FIELDS_IN_EXECUTIVE_VMCS = 25, + VMXERR_ENTRY_EVENTS_BLOCKED_BY_MOV_SS = 26, + VMXERR_INVALID_OPERAND_TO_INVEPT_INVVPID = 28, +}; + #define SAVE_GPR \ "xchg %rax, regs\n\t" \ "xchg %rbx, regs+0x8\n\t" \ @@ -451,10 +503,14 @@ enum Intr_type { #define VMX_TEST_VMEXIT 1 #define VMX_TEST_EXIT 2 #define VMX_TEST_RESUME 3 +#define VMX_TEST_VMABORT 4 +#define VMX_TEST_VMSKIP 5 #define HYPERCALL_BIT (1ul << 12) #define HYPERCALL_MASK 0xFFF #define HYPERCALL_VMEXIT 0x1 +#define HYPERCALL_VMABORT 0x2 +#define HYPERCALL_VMSKIP 0x3 #define EPTP_PG_WALK_LEN_SHIFT 3ul #define EPTP_AD_FLAG (1ul << 6) @@ -487,8 +543,10 @@ enum Intr_type { #define EPT_CAP_INVEPT_ALL (1ull << 26) #define EPT_CAP_AD_FLAG (1ull << 21) #define VPID_CAP_INVVPID (1ull << 32) -#define VPID_CAP_INVVPID_SINGLE (1ull << 41) -#define VPID_CAP_INVVPID_ALL (1ull << 42) +#define VPID_CAP_INVVPID_ADDR (1ull << 40) +#define VPID_CAP_INVVPID_CXTGLB (1ull << 41) +#define VPID_CAP_INVVPID_ALL (1ull << 42) +#define VPID_CAP_INVVPID_CXTLOC (1ull << 43) #define PAGE_SIZE_2M (512 * PAGE_SIZE) #define PAGE_SIZE_1G (512 * PAGE_SIZE_2M) @@ -506,19 +564,23 @@ enum Intr_type { #define EPT_VLT_PERM_RD (1 << 3) #define EPT_VLT_PERM_WR (1 << 4) #define EPT_VLT_PERM_EX (1 << 5) +#define EPT_VLT_PERMS (EPT_VLT_PERM_RD | EPT_VLT_PERM_WR | \ + EPT_VLT_PERM_EX) #define EPT_VLT_LADDR_VLD (1 << 7) #define EPT_VLT_PADDR (1 << 8) #define MAGIC_VAL_1 0x12345678ul #define MAGIC_VAL_2 0x87654321ul #define MAGIC_VAL_3 0xfffffffful +#define MAGIC_VAL_4 0xdeadbeeful #define INVEPT_SINGLE 1 #define INVEPT_GLOBAL 2 -#define INVVPID_SINGLE_ADDRESS 0 -#define INVVPID_SINGLE 1 +#define INVVPID_ADDR 0 +#define INVVPID_CONTEXT_GLOBAL 1 #define INVVPID_ALL 2 +#define INVVPID_CONTEXT_LOCAL 3 #define ACTV_ACTIVE 0 #define ACTV_HLT 1 @@ -532,10 +594,41 @@ extern union vmx_ctrl_msr ctrl_exit_rev; extern union vmx_ctrl_msr ctrl_enter_rev; extern union vmx_ept_vpid ept_vpid; +extern u64 *vmxon_region; + void vmx_set_test_stage(u32 s); u32 vmx_get_test_stage(void); void vmx_inc_test_stage(void); +static int vmx_on(void) +{ + bool ret; + u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + asm volatile ("push %1; popf; vmxon %2; setbe %0\n\t" + : "=q" (ret) : "q" (rflags), "m" (vmxon_region) : "cc"); + return ret; +} + +static int vmx_off(void) +{ + bool ret; + u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + + asm volatile("push %1; popf; vmxoff; setbe %0\n\t" + : "=q"(ret) : "q" (rflags) : "cc"); + return ret; +} + +static inline int make_vmcs_current(struct vmcs *vmcs) +{ + bool ret; + u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + + asm volatile ("push %1; popf; vmptrld %2; setbe %0" + : "=q" (ret) : "q" (rflags), "m" (vmcs) : "cc"); + return ret; +} + static inline int vmcs_clear(struct vmcs *vmcs) { bool ret; @@ -553,6 +646,25 @@ static inline u64 vmcs_read(enum Encoding enc) return val; } +static inline int vmcs_read_checking(enum Encoding enc, u64 *value) +{ + u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + u64 encoding = enc; + u64 val; + + asm volatile ("shl $8, %%rax;" + "sahf;" + "vmread %[encoding], %[val];" + "lahf;" + "shr $8, %%rax" + : /* output */ [val]"=rm"(val), "+a"(rflags) + : /* input */ [encoding]"r"(encoding) + : /* clobber */ "cc"); + + *value = val; + return rflags & (X86_EFLAGS_CF | X86_EFLAGS_ZF); +} + static inline int vmcs_write(enum Encoding enc, u64 val) { bool ret; @@ -564,10 +676,12 @@ static inline int vmcs_write(enum Encoding enc, u64 val) static inline int vmcs_save(struct vmcs **vmcs) { bool ret; + unsigned long pa; u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; - asm volatile ("push %1; popf; vmptrst %2; setbe %0" - : "=q" (ret) : "q" (rflags), "m" (*vmcs) : "cc"); + asm volatile ("push %2; popf; vmptrst %1; setbe %0" + : "=q" (ret), "=m" (pa) : "r" (rflags) : "cc"); + *vmcs = (pa == -1ull) ? NULL : phys_to_virt(pa); return ret; } @@ -584,21 +698,18 @@ static inline bool invept(unsigned long type, u64 eptp) return ret; } -static inline bool invvpid(unsigned long type, u16 vpid, u64 gva) +static inline bool invvpid(unsigned long type, u64 vpid, u64 gla) { bool ret; u64 rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; - struct { - u64 vpid : 16; - u64 rsvd : 48; - u64 gva; - } operand = {vpid, 0, gva}; + struct invvpid_operand operand = {vpid, gla}; asm volatile("push %1; popf; invvpid %2, %3; setbe %0" : "=q" (ret) : "r" (rflags), "m"(operand),"r"(type) : "cc"); return ret; } +const char *exit_reason_description(u64 reason); void print_vmexit_info(); void print_vmentry_failure_info(struct vmentry_failure *failure); void ept_sync(int type, u64 eptp); @@ -614,9 +725,83 @@ void install_ept(unsigned long *pml4, unsigned long phys, unsigned long guest_addr, u64 perm); void setup_ept_range(unsigned long *pml4, unsigned long start, unsigned long len, int map_1g, int map_2m, u64 perm); -unsigned long get_ept_pte(unsigned long *pml4, - unsigned long guest_addr, int level); -int set_ept_pte(unsigned long *pml4, unsigned long guest_addr, +bool get_ept_pte(unsigned long *pml4, unsigned long guest_addr, int level, + unsigned long *pte); +void set_ept_pte(unsigned long *pml4, unsigned long guest_addr, int level, u64 pte_val); +void check_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr, int expected_gpa_ad, + int expected_pt_ad); +void clear_ept_ad(unsigned long *pml4, u64 guest_cr3, + unsigned long guest_addr); + +bool ept_2m_supported(void); +bool ept_1g_supported(void); +bool ept_huge_pages_supported(int level); +bool ept_execute_only_supported(void); +bool ept_ad_bits_supported(void); + +void enter_guest(void); + +typedef void (*test_guest_func)(void); +typedef void (*test_teardown_func)(void *data); +void test_set_guest(test_guest_func func); +void test_add_teardown(test_teardown_func func, void *data); +void test_skip(const char *msg); + +void __abort_test(void); + +#define TEST_ASSERT(cond) \ +do { \ + if (!(cond)) { \ + report("%s:%d: Assertion failed: %s", 0, \ + __FILE__, __LINE__, #cond); \ + dump_stack(); \ + __abort_test(); \ + } \ + report_pass(); \ +} while (0) + +#define TEST_ASSERT_MSG(cond, fmt, args...) \ +do { \ + if (!(cond)) { \ + report("%s:%d: Assertion failed: %s\n" fmt, 0, \ + __FILE__, __LINE__, #cond, ##args); \ + dump_stack(); \ + __abort_test(); \ + } \ + report_pass(); \ +} while (0) + +#define __TEST_EQ(a, b, a_str, b_str, assertion, fmt, args...) \ +do { \ + typeof(a) _a = a; \ + typeof(b) _b = b; \ + if (_a != _b) { \ + char _bin_a[BINSTR_SZ]; \ + char _bin_b[BINSTR_SZ]; \ + binstr(_a, _bin_a); \ + binstr(_b, _bin_b); \ + report("%s:%d: %s failed: (%s) == (%s)\n" \ + "\tLHS: %#018lx - %s - %lu\n" \ + "\tRHS: %#018lx - %s - %lu%s" fmt, 0, \ + __FILE__, __LINE__, \ + assertion ? "Assertion" : "Expectation", a_str, b_str, \ + (unsigned long) _a, _bin_a, (unsigned long) _a, \ + (unsigned long) _b, _bin_b, (unsigned long) _b, \ + fmt[0] == '\0' ? "" : "\n", ## args); \ + dump_stack(); \ + if (assertion) \ + __abort_test(); \ + } \ + report_pass(); \ +} while (0) + +#define TEST_ASSERT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 1, "") +#define TEST_ASSERT_EQ_MSG(a, b, fmt, args...) \ + __TEST_EQ(a, b, #a, #b, 1, fmt, ## args) +#define TEST_EXPECT_EQ(a, b) __TEST_EQ(a, b, #a, #b, 0, "") +#define TEST_EXPECT_EQ_MSG(a, b, fmt, args...) \ + __TEST_EQ(a, b, #a, #b, 0, fmt, ## args) #endif diff --git a/tests/kvm-unit-tests/x86/vmx_tests.c b/tests/kvm-unit-tests/x86/vmx_tests.c index fff12141..0ccc7e77 100644 --- a/tests/kvm-unit-tests/x86/vmx_tests.c +++ b/tests/kvm-unit-tests/x86/vmx_tests.c @@ -13,6 +13,10 @@ #include "apic.h" #include "types.h" +#define NONCANONICAL 0xaaaaaaaaaaaaaaaaull + +#define VPID_CAP_INVVPID_TYPES_SHIFT 40 + u64 ia32_pat; u64 ia32_efer; void *io_bitmap_a, *io_bitmap_b; @@ -22,6 +26,18 @@ unsigned long *pml4; u64 eptp; void *data_page1, *data_page2; +void *pml_log; +#define PML_INDEX 512 + +static inline unsigned ffs(unsigned x) +{ + int pos = -1; + + __asm__ __volatile__("bsf %1, %%eax; cmovnz %%eax, %0" + : "+r"(pos) : "rm"(x) : "eax"); + return pos + 1; +} + static inline void vmcall() { asm volatile("vmcall"); @@ -29,6 +45,7 @@ static inline void vmcall() void basic_guest_main() { + report("Basic VMX test", 1); } int basic_exit_handler() @@ -163,7 +180,7 @@ int preemption_timer_exit_handler() saved_rip == guest_rip); break; default: - printf("Invalid stage.\n"); + report("Invalid stage.", false); print_vmexit_info(); break; } @@ -204,14 +221,14 @@ int preemption_timer_exit_handler() break; default: // Should not reach here - printf("ERROR : unexpected stage, %d\n", + report("unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; } break; default: - printf("Unknown exit reason, %ld\n", reason); + report("Unknown exit reason, %ld", false, reason); print_vmexit_info(); } vmcs_write(PIN_CONTROLS, vmcs_read(PIN_CONTROLS) & ~PIN_PREEMPT); @@ -508,7 +525,7 @@ static int cr_shadowing_exit_handler() break; default: // Should not reach here - printf("ERROR : unexpected stage, %d\n", + report("unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -547,7 +564,7 @@ static int cr_shadowing_exit_handler() break; default: // Should not reach here - printf("ERROR : unexpected stage, %d\n", + report("unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -555,7 +572,7 @@ static int cr_shadowing_exit_handler() vmcs_write(GUEST_RIP, guest_rip + insn_len); return VMX_TEST_RESUME; default: - printf("Unknown exit reason, %ld\n", reason); + report("Unknown exit reason, %ld", false, reason); print_vmexit_info(); } return VMX_TEST_VMEXIT; @@ -686,7 +703,7 @@ static int iobmp_exit_handler() break; default: // Should not reach here - printf("ERROR : unexpected stage, %d\n", + report("unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -707,7 +724,7 @@ static int iobmp_exit_handler() break; default: // Should not reach here - printf("ERROR : unexpected stage, %d\n", + report("unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -715,7 +732,7 @@ static int iobmp_exit_handler() vmcs_write(GUEST_RIP, guest_rip + insn_len); return VMX_TEST_RESUME; default: - printf("guest_rip = 0x%lx\n", guest_rip); + printf("guest_rip = %#lx\n", guest_rip); printf("\tERROR : Undefined exit reason, reason = %ld.\n", reason); break; } @@ -732,7 +749,7 @@ static int iobmp_exit_handler() asm( "insn_hlt: hlt;ret\n\t" "insn_invlpg: invlpg 0x12345678;ret\n\t" - "insn_mwait: mwait;ret\n\t" + "insn_mwait: xor %eax, %eax; xor %ecx, %ecx; mwait;ret\n\t" "insn_rdpmc: xor %ecx, %ecx; rdpmc;ret\n\t" "insn_rdtsc: rdtsc;ret\n\t" "insn_cr3_load: mov cr3,%rax; mov %rax,%cr3;ret\n\t" @@ -741,7 +758,7 @@ asm( "insn_cr8_load: mov %rax,%cr8;ret\n\t" "insn_cr8_store: mov %cr8,%rax;ret\n\t" #endif - "insn_monitor: monitor;ret\n\t" + "insn_monitor: xor %eax, %eax; xor %ecx, %ecx; xor %edx, %edx; monitor;ret\n\t" "insn_pause: pause;ret\n\t" "insn_wbinvd: wbinvd;ret\n\t" "insn_cpuid: mov $10, %eax; cpuid;ret\n\t" @@ -939,10 +956,18 @@ static int insn_intercept_exit_handler() } -static int setup_ept() +/* Enables EPT and sets up the identity map. */ +static int setup_ept(bool enable_ad) { - int support_2m; unsigned long end_of_memory; + u32 ctrl_cpu[2]; + + if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) || + !(ctrl_cpu_rev[1].clr & CPU_EPT)) { + printf("\tEPT is not supported"); + return 1; + } + if (!(ept_vpid.val & EPT_CAP_UC) && !(ept_vpid.val & EPT_CAP_WB)) { @@ -958,32 +983,6 @@ static int setup_ept() printf("\tPWL4 is not supported\n"); return 1; } - eptp |= (3 << EPTP_PG_WALK_LEN_SHIFT); - pml4 = alloc_page(); - memset(pml4, 0, PAGE_SIZE); - eptp |= virt_to_phys(pml4); - vmcs_write(EPTP, eptp); - support_2m = !!(ept_vpid.val & EPT_CAP_2M_PAGE); - end_of_memory = fwcfg_get_u64(FW_CFG_RAM_SIZE); - if (end_of_memory < (1ul << 32)) - end_of_memory = (1ul << 32); - setup_ept_range(pml4, 0, end_of_memory, 0, support_2m, - EPT_WA | EPT_RA | EPT_EA); - return 0; -} - -static int apic_version; - -static int ept_init() -{ - u32 ctrl_cpu[2]; - - if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) || - !(ctrl_cpu_rev[1].clr & CPU_EPT)) { - printf("\tEPT is not supported"); - return VMX_TEST_EXIT; - } - ctrl_cpu[0] = vmcs_read(CPU_EXEC_CTRL0); ctrl_cpu[1] = vmcs_read(CPU_EXEC_CTRL1); ctrl_cpu[0] = (ctrl_cpu[0] | CPU_SECONDARY) @@ -992,7 +991,49 @@ static int ept_init() & ctrl_cpu_rev[1].clr; vmcs_write(CPU_EXEC_CTRL0, ctrl_cpu[0]); vmcs_write(CPU_EXEC_CTRL1, ctrl_cpu[1]); - if (setup_ept()) + eptp |= (3 << EPTP_PG_WALK_LEN_SHIFT); + pml4 = alloc_page(); + memset(pml4, 0, PAGE_SIZE); + eptp |= virt_to_phys(pml4); + if (enable_ad) + eptp |= EPTP_AD_FLAG; + vmcs_write(EPTP, eptp); + end_of_memory = fwcfg_get_u64(FW_CFG_RAM_SIZE); + if (end_of_memory < (1ul << 32)) + end_of_memory = (1ul << 32); + /* Cannot use large EPT pages if we need to track EPT + * accessed/dirty bits at 4K granularity. + */ + setup_ept_range(pml4, 0, end_of_memory, 0, + !enable_ad && ept_2m_supported(), + EPT_WA | EPT_RA | EPT_EA); + return 0; +} + +static void ept_enable_ad_bits(void) +{ + eptp |= EPTP_AD_FLAG; + vmcs_write(EPTP, eptp); +} + +static void ept_disable_ad_bits(void) +{ + eptp &= ~EPTP_AD_FLAG; + vmcs_write(EPTP, eptp); +} + +static void ept_enable_ad_bits_or_skip_test(void) +{ + if (!ept_ad_bits_supported()) + test_skip("EPT AD bits not supported."); + ept_enable_ad_bits(); +} + +static int apic_version; + +static int ept_init_common(bool have_ad) +{ + if (setup_ept(have_ad)) return VMX_TEST_EXIT; data_page1 = alloc_page(); data_page2 = alloc_page(); @@ -1003,11 +1044,16 @@ static int ept_init() install_ept(pml4, (unsigned long)data_page1, (unsigned long)data_page2, EPT_RA | EPT_WA | EPT_EA); - apic_version = *((u32 *)0xfee00030UL); + apic_version = apic_read(APIC_LVR); return VMX_TEST_START; } -static void ept_main() +static int ept_init() +{ + return ept_init_common(false); +} + +static void ept_common() { vmx_set_test_stage(0); if (*((u32 *)data_page2) != MAGIC_VAL_1 || @@ -1047,6 +1093,11 @@ t1: vmcall(); *((u32 *)data_page1) = MAGIC_VAL_2; report("EPT violation - paging structure", vmx_get_test_stage() == 5); +} + +static void ept_main() +{ + ept_common(); // Test EPT access to L1 MMIO vmx_set_test_stage(6); @@ -1075,15 +1126,63 @@ bool invept_test(int type, u64 eptp) return true; } -static int ept_exit_handler() +static int pml_exit_handler(void) +{ + u16 index, count; + ulong reason = vmcs_read(EXI_REASON) & 0xff; + u64 *pmlbuf = pml_log; + u64 guest_rip = vmcs_read(GUEST_RIP);; + u64 guest_cr3 = vmcs_read(GUEST_CR3); + u32 insn_len = vmcs_read(EXI_INST_LEN); + + switch (reason) { + case VMX_VMCALL: + switch (vmx_get_test_stage()) { + case 0: + index = vmcs_read(GUEST_PML_INDEX); + for (count = index + 1; count < PML_INDEX; count++) { + if (pmlbuf[count] == (u64)data_page2) { + vmx_inc_test_stage(); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2); + break; + } + } + break; + case 1: + index = vmcs_read(GUEST_PML_INDEX); + /* Keep clearing the dirty bit till a overflow */ + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2); + break; + default: + report("unexpected stage, %d.", false, + vmx_get_test_stage()); + print_vmexit_info(); + return VMX_TEST_VMEXIT; + } + vmcs_write(GUEST_RIP, guest_rip + insn_len); + return VMX_TEST_RESUME; + case VMX_PML_FULL: + vmx_inc_test_stage(); + vmcs_write(GUEST_PML_INDEX, PML_INDEX - 1); + return VMX_TEST_RESUME; + default: + report("Unknown exit reason, %ld", false, reason); + print_vmexit_info(); + } + return VMX_TEST_VMEXIT; +} + +static int ept_exit_handler_common(bool have_ad) { u64 guest_rip; + u64 guest_cr3; ulong reason; u32 insn_len; u32 exit_qual; static unsigned long data_page1_pte, data_page1_pte_pte; guest_rip = vmcs_read(GUEST_RIP); + guest_cr3 = vmcs_read(GUEST_CR3); reason = vmcs_read(EXI_REASON) & 0xff; insn_len = vmcs_read(EXI_INST_LEN); exit_qual = vmcs_read(EXI_QUALIFICATION); @@ -1091,6 +1190,18 @@ static int ept_exit_handler() case VMX_VMCALL: switch (vmx_get_test_stage()) { case 0: + check_ept_ad(pml4, guest_cr3, + (unsigned long)data_page1, + have_ad ? EPT_ACCESS_FLAG : 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + check_ept_ad(pml4, guest_cr3, + (unsigned long)data_page2, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page2); + if (have_ad) + ept_sync(INVEPT_SINGLE, eptp);; if (*((u32 *)data_page1) == MAGIC_VAL_3 && *((u32 *)data_page2) == MAGIC_VAL_2) { vmx_inc_test_stage(); @@ -1113,19 +1224,21 @@ static int ept_exit_handler() ept_sync(INVEPT_SINGLE, eptp); break; case 3: - data_page1_pte = get_ept_pte(pml4, - (unsigned long)data_page1, 1); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); + TEST_ASSERT(get_ept_pte(pml4, (unsigned long)data_page1, + 1, &data_page1_pte)); set_ept_pte(pml4, (unsigned long)data_page1, - 1, data_page1_pte & (~EPT_PRESENT)); + 1, data_page1_pte & ~EPT_PRESENT); ept_sync(INVEPT_SINGLE, eptp); break; case 4: - data_page1_pte = get_ept_pte(pml4, - (unsigned long)data_page1, 2); + TEST_ASSERT(get_ept_pte(pml4, (unsigned long)data_page1, + 2, &data_page1_pte)); data_page1_pte &= PAGE_MASK; - data_page1_pte_pte = get_ept_pte(pml4, data_page1_pte, 2); + TEST_ASSERT(get_ept_pte(pml4, data_page1_pte, + 2, &data_page1_pte_pte)); set_ept_pte(pml4, data_page1_pte, 2, - data_page1_pte_pte & (~EPT_PRESENT)); + data_page1_pte_pte & ~EPT_PRESENT); ept_sync(INVEPT_SINGLE, eptp); break; case 6: @@ -1134,7 +1247,7 @@ static int ept_exit_handler() break; // Should not reach here default: - printf("ERROR - unexpected stage, %d.\n", + report("ERROR - unexpected stage, %d.", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -1153,7 +1266,7 @@ static int ept_exit_handler() break; // Should not reach here default: - printf("ERROR - unexpected stage, %d.\n", + report("ERROR - unexpected stage, %d.", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -1162,15 +1275,23 @@ static int ept_exit_handler() case VMX_EPT_VIOLATION: switch(vmx_get_test_stage()) { case 3: + check_ept_ad(pml4, guest_cr3, (unsigned long)data_page1, 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); if (exit_qual == (EPT_VLT_WR | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR)) vmx_inc_test_stage(); - set_ept_pte(pml4, (unsigned long)data_page1, + set_ept_pte(pml4, (unsigned long)data_page1, 1, data_page1_pte | (EPT_PRESENT)); ept_sync(INVEPT_SINGLE, eptp); break; case 4: - if (exit_qual == (EPT_VLT_RD | EPT_VLT_LADDR_VLD)) + check_ept_ad(pml4, guest_cr3, (unsigned long)data_page1, 0, + have_ad ? EPT_ACCESS_FLAG | EPT_DIRTY_FLAG : 0); + clear_ept_ad(pml4, guest_cr3, (unsigned long)data_page1); + if (exit_qual == (EPT_VLT_RD | + (have_ad ? EPT_VLT_WR : 0) | + EPT_VLT_LADDR_VLD)) vmx_inc_test_stage(); set_ept_pte(pml4, data_page1_pte, 2, data_page1_pte_pte | (EPT_PRESENT)); @@ -1178,24 +1299,98 @@ static int ept_exit_handler() break; default: // Should not reach here - printf("ERROR : unexpected stage, %d\n", + report("ERROR : unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; } return VMX_TEST_RESUME; default: - printf("Unknown exit reason, %ld\n", reason); + report("Unknown exit reason, %ld", false, reason); print_vmexit_info(); } return VMX_TEST_VMEXIT; } +static int ept_exit_handler() +{ + return ept_exit_handler_common(false); +} + +static int eptad_init() +{ + int r = ept_init_common(true); + + if (r == VMX_TEST_EXIT) + return r; + + if ((rdmsr(MSR_IA32_VMX_EPT_VPID_CAP) & EPT_CAP_AD_FLAG) == 0) { + printf("\tEPT A/D bits are not supported"); + return VMX_TEST_EXIT; + } + + return r; +} + +static int pml_init() +{ + u32 ctrl_cpu; + int r = eptad_init(); + + if (r == VMX_TEST_EXIT) + return r; + + if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) || + !(ctrl_cpu_rev[1].clr & CPU_PML)) { + printf("\tPML is not supported"); + return VMX_TEST_EXIT; + } + + pml_log = alloc_page(); + memset(pml_log, 0x0, PAGE_SIZE); + vmcs_write(PMLADDR, (u64)pml_log); + vmcs_write(GUEST_PML_INDEX, PML_INDEX - 1); + + ctrl_cpu = vmcs_read(CPU_EXEC_CTRL1) | CPU_PML; + vmcs_write(CPU_EXEC_CTRL1, ctrl_cpu); + + return VMX_TEST_START; +} + +static void pml_main() +{ + int count = 0; + + vmx_set_test_stage(0); + *((u32 *)data_page2) = 0x1; + vmcall(); + report("PML - Dirty GPA Logging", vmx_get_test_stage() == 1); + + while (vmx_get_test_stage() == 1) { + vmcall(); + *((u32 *)data_page2) = 0x1; + if (count++ > PML_INDEX) + break; + } + report("PML Full Event", vmx_get_test_stage() == 2); +} + +static void eptad_main() +{ + ept_common(); +} + +static int eptad_exit_handler() +{ + return ept_exit_handler_common(true); +} + bool invvpid_test(int type, u16 vpid) { bool ret, supported; - supported = ept_vpid.val & (VPID_CAP_INVVPID_SINGLE >> INVVPID_SINGLE << type); + supported = ept_vpid.val & + (VPID_CAP_INVVPID_ADDR >> INVVPID_ADDR << type); ret = invvpid(type, vpid, 0); if (ret == !supported) @@ -1252,11 +1447,11 @@ static int vpid_exit_handler() case VMX_VMCALL: switch(vmx_get_test_stage()) { case 0: - if (!invvpid_test(INVVPID_SINGLE_ADDRESS, 1)) + if (!invvpid_test(INVVPID_ADDR, 1)) vmx_inc_test_stage(); break; case 2: - if (!invvpid_test(INVVPID_SINGLE, 1)) + if (!invvpid_test(INVVPID_CONTEXT_GLOBAL, 1)) vmx_inc_test_stage(); break; case 4: @@ -1264,7 +1459,7 @@ static int vpid_exit_handler() vmx_inc_test_stage(); break; default: - printf("ERROR: unexpected stage, %d\n", + report("ERROR: unexpected stage, %d", false, vmx_get_test_stage()); print_vmexit_info(); return VMX_TEST_VMEXIT; @@ -1272,7 +1467,7 @@ static int vpid_exit_handler() vmcs_write(GUEST_RIP, guest_rip + insn_len); return VMX_TEST_RESUME; default: - printf("Unknown exit reason, %ld\n", reason); + report("Unknown exit reason, %ld", false, reason); print_vmexit_info(); } return VMX_TEST_VMEXIT; @@ -1429,7 +1624,7 @@ static int interrupt_exit_handler(void) vmcs_write(GUEST_ACTV_STATE, ACTV_ACTIVE); return VMX_TEST_RESUME; default: - printf("Unknown exit reason, %ld\n", reason); + report("Unknown exit reason, %ld", false, reason); print_vmexit_info(); } @@ -1542,7 +1737,7 @@ static int dbgctls_exit_handler(void) vmcs_write(GUEST_RIP, guest_rip + insn_len); return VMX_TEST_RESUME; default: - printf("Unknown exit reason, %d\n", reason); + report("Unknown exit reason, %d", false, reason); print_vmexit_info(); } return VMX_TEST_VMEXIT; @@ -1664,7 +1859,7 @@ static int vmmcall_exit_handler() (vmcs_read(EXI_INTR_INFO) & 0xff) == UD_VECTOR); break; default: - printf("Unknown exit reason, %ld\n", reason); + report("Unknown exit reason, %ld", false, reason); print_vmexit_info(); } @@ -1736,7 +1931,7 @@ static int disable_rdtscp_exit_handler(void) break; default: - printf("Unknown exit reason, %d\n", reason); + report("Unknown exit reason, %d", false, reason); print_vmexit_info(); } return VMX_TEST_VMEXIT; @@ -1816,6 +2011,1385 @@ int into_exit_handler() return VMX_TEST_VMEXIT; } +static void exit_monitor_from_l2_main(void) +{ + printf("Calling exit(0) from l2...\n"); + exit(0); +} + +static int exit_monitor_from_l2_handler(void) +{ + report("The guest should have killed the VMM", false); + return VMX_TEST_EXIT; +} + +static void assert_exit_reason(u64 expected) +{ + u64 actual = vmcs_read(EXI_REASON); + + TEST_ASSERT_EQ_MSG(expected, actual, "Expected %s, got %s.", + exit_reason_description(expected), + exit_reason_description(actual)); +} + +static void skip_exit_vmcall() +{ + u64 guest_rip = vmcs_read(GUEST_RIP); + u32 insn_len = vmcs_read(EXI_INST_LEN); + + assert_exit_reason(VMX_VMCALL); + vmcs_write(GUEST_RIP, guest_rip + insn_len); +} + +static void v2_null_test_guest(void) +{ +} + +static void v2_null_test(void) +{ + test_set_guest(v2_null_test_guest); + enter_guest(); + report(__func__, 1); +} + +static void v2_multiple_entries_test_guest(void) +{ + vmx_set_test_stage(1); + vmcall(); + vmx_set_test_stage(2); +} + +static void v2_multiple_entries_test(void) +{ + test_set_guest(v2_multiple_entries_test_guest); + enter_guest(); + TEST_ASSERT_EQ(vmx_get_test_stage(), 1); + skip_exit_vmcall(); + enter_guest(); + TEST_ASSERT_EQ(vmx_get_test_stage(), 2); + report(__func__, 1); +} + +static int fixture_test_data = 1; + +static void fixture_test_teardown(void *data) +{ + *((int *) data) = 1; +} + +static void fixture_test_guest(void) +{ + fixture_test_data++; +} + + +static void fixture_test_setup(void) +{ + TEST_ASSERT_EQ_MSG(1, fixture_test_data, + "fixture_test_teardown didn't run?!"); + fixture_test_data = 2; + test_add_teardown(fixture_test_teardown, &fixture_test_data); + test_set_guest(fixture_test_guest); +} + +static void fixture_test_case1(void) +{ + fixture_test_setup(); + TEST_ASSERT_EQ(2, fixture_test_data); + enter_guest(); + TEST_ASSERT_EQ(3, fixture_test_data); + report(__func__, 1); +} + +static void fixture_test_case2(void) +{ + fixture_test_setup(); + TEST_ASSERT_EQ(2, fixture_test_data); + enter_guest(); + TEST_ASSERT_EQ(3, fixture_test_data); + report(__func__, 1); +} + +enum ept_access_op { + OP_READ, + OP_WRITE, + OP_EXEC, + OP_FLUSH_TLB, + OP_EXIT, +}; + +static struct ept_access_test_data { + unsigned long gpa; + unsigned long *gva; + unsigned long hpa; + unsigned long *hva; + enum ept_access_op op; +} ept_access_test_data; + +extern unsigned char ret42_start; +extern unsigned char ret42_end; + +/* Returns 42. */ +asm( + ".align 64\n" + "ret42_start:\n" + "mov $42, %eax\n" + "ret\n" + "ret42_end:\n" +); + +static void +diagnose_ept_violation_qual(u64 expected, u64 actual) +{ + +#define DIAGNOSE(flag) \ +do { \ + if ((expected & flag) != (actual & flag)) \ + printf(#flag " %sexpected\n", \ + (expected & flag) ? "" : "un"); \ +} while (0) + + DIAGNOSE(EPT_VLT_RD); + DIAGNOSE(EPT_VLT_WR); + DIAGNOSE(EPT_VLT_FETCH); + DIAGNOSE(EPT_VLT_PERM_RD); + DIAGNOSE(EPT_VLT_PERM_WR); + DIAGNOSE(EPT_VLT_PERM_EX); + DIAGNOSE(EPT_VLT_LADDR_VLD); + DIAGNOSE(EPT_VLT_PADDR); + +#undef DIAGNOSE +} + +static void do_ept_access_op(enum ept_access_op op) +{ + ept_access_test_data.op = op; + enter_guest(); +} + +/* + * Force the guest to flush its TLB (i.e., flush gva -> gpa mappings). Only + * needed by tests that modify guest PTEs. + */ +static void ept_access_test_guest_flush_tlb(void) +{ + do_ept_access_op(OP_FLUSH_TLB); + skip_exit_vmcall(); +} + +/* + * Modifies the EPT entry at @level in the mapping of @gpa. First clears the + * bits in @clear then sets the bits in @set. @mkhuge transforms the entry into + * a huge page. + */ +static unsigned long ept_twiddle(unsigned long gpa, bool mkhuge, int level, + unsigned long clear, unsigned long set) +{ + struct ept_access_test_data *data = &ept_access_test_data; + unsigned long orig_pte; + unsigned long pte; + + /* Screw with the mapping at the requested level. */ + TEST_ASSERT(get_ept_pte(pml4, gpa, level, &orig_pte)); + pte = orig_pte; + if (mkhuge) + pte = (orig_pte & ~EPT_ADDR_MASK) | data->hpa | EPT_LARGE_PAGE; + else + pte = orig_pte; + pte = (pte & ~clear) | set; + set_ept_pte(pml4, gpa, level, pte); + ept_sync(INVEPT_SINGLE, eptp); + + return orig_pte; +} + +static void ept_untwiddle(unsigned long gpa, int level, unsigned long orig_pte) +{ + set_ept_pte(pml4, gpa, level, orig_pte); +} + +static void do_ept_violation(bool leaf, enum ept_access_op op, + u64 expected_qual, u64 expected_paddr) +{ + u64 qual; + + /* Try the access and observe the violation. */ + do_ept_access_op(op); + + assert_exit_reason(VMX_EPT_VIOLATION); + + qual = vmcs_read(EXI_QUALIFICATION); + + diagnose_ept_violation_qual(expected_qual, qual); + TEST_EXPECT_EQ(expected_qual, qual); + + #if 0 + /* Disable for now otherwise every test will fail */ + TEST_EXPECT_EQ(vmcs_read(GUEST_LINEAR_ADDRESS), + (unsigned long) ( + op == OP_EXEC ? data->gva + 1 : data->gva)); + #endif + /* + * TODO: tests that probe expected_paddr in pages other than the one at + * the beginning of the 1g region. + */ + TEST_EXPECT_EQ(vmcs_read(INFO_PHYS_ADDR), expected_paddr); +} + +static void +ept_violation_at_level_mkhuge(bool mkhuge, int level, unsigned long clear, + unsigned long set, enum ept_access_op op, + u64 expected_qual) +{ + struct ept_access_test_data *data = &ept_access_test_data; + unsigned long orig_pte; + + orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set); + + do_ept_violation(level == 1 || mkhuge, op, expected_qual, + op == OP_EXEC ? data->gpa + sizeof(unsigned long) : + data->gpa); + + /* Fix the violation and resume the op loop. */ + ept_untwiddle(data->gpa, level, orig_pte); + enter_guest(); + skip_exit_vmcall(); +} + +static void +ept_violation_at_level(int level, unsigned long clear, unsigned long set, + enum ept_access_op op, u64 expected_qual) +{ + ept_violation_at_level_mkhuge(false, level, clear, set, op, + expected_qual); + if (ept_huge_pages_supported(level)) + ept_violation_at_level_mkhuge(true, level, clear, set, op, + expected_qual); +} + +static void ept_violation(unsigned long clear, unsigned long set, + enum ept_access_op op, u64 expected_qual) +{ + ept_violation_at_level(1, clear, set, op, expected_qual); + ept_violation_at_level(2, clear, set, op, expected_qual); + ept_violation_at_level(3, clear, set, op, expected_qual); + ept_violation_at_level(4, clear, set, op, expected_qual); +} + +static void ept_access_violation(unsigned long access, enum ept_access_op op, + u64 expected_qual) +{ + ept_violation(EPT_PRESENT, access, op, + expected_qual | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR); +} + +/* + * For translations that don't involve a GVA, that is physical address (paddr) + * accesses, EPT violations don't set the flag EPT_VLT_PADDR. For a typical + * guest memory access, the hardware does GVA -> GPA -> HPA. However, certain + * translations don't involve GVAs, such as when the hardware does the guest + * page table walk. For example, in translating GVA_1 -> GPA_1, the guest MMU + * might try to set an A bit on a guest PTE. If the GPA_2 that the PTE resides + * on isn't present in the EPT, then the EPT violation will be for GPA_2 and + * the EPT_VLT_PADDR bit will be clear in the exit qualification. + * + * Note that paddr violations can also be triggered by loading PAE page tables + * with wonky addresses. We don't test that yet. + * + * This function modifies the EPT entry that maps the GPA that the guest page + * table entry mapping ept_access_data.gva resides on. + * + * @ept_access EPT permissions to set. Other permissions are cleared. + * + * @pte_ad Set the A/D bits on the guest PTE accordingly. + * + * @op Guest operation to perform with ept_access_data.gva. + * + * @expect_violation + * Is a violation expected during the paddr access? + * + * @expected_qual Expected qualification for the EPT violation. + * EPT_VLT_PADDR should be clear. + */ +static void ept_access_paddr(unsigned long ept_access, unsigned long pte_ad, + enum ept_access_op op, bool expect_violation, + u64 expected_qual) +{ + struct ept_access_test_data *data = &ept_access_test_data; + unsigned long *ptep; + unsigned long gpa; + unsigned long orig_epte; + + /* Modify the guest PTE mapping data->gva according to @pte_ad. */ + ptep = get_pte_level(current_page_table(), data->gva, /*level=*/1); + TEST_ASSERT(ptep); + TEST_ASSERT_EQ(*ptep & PT_ADDR_MASK, data->gpa); + *ptep = (*ptep & ~PT_AD_MASK) | pte_ad; + ept_access_test_guest_flush_tlb(); + + /* + * Now modify the access bits on the EPT entry for the GPA that the + * guest PTE resides on. Note that by modifying a single EPT entry, + * we're potentially affecting 512 guest PTEs. However, we've carefully + * constructed our test such that those other 511 PTEs aren't used by + * the guest: data->gva is at the beginning of a 1G huge page, thus the + * PTE we're modifying is at the beginning of a 4K page and the + * following 511 entires are also under our control (and not touched by + * the guest). + */ + gpa = virt_to_phys(ptep); + TEST_ASSERT_EQ(gpa & ~PAGE_MASK, 0); + /* + * Make sure the guest page table page is mapped with a 4K EPT entry, + * otherwise our level=1 twiddling below will fail. We use the + * identity map (gpa = gpa) since page tables are shared with the host. + */ + install_ept(pml4, gpa, gpa, EPT_PRESENT); + orig_epte = ept_twiddle(gpa, /*mkhuge=*/0, /*level=*/1, + /*clear=*/EPT_PRESENT, /*set=*/ept_access); + + if (expect_violation) { + do_ept_violation(/*leaf=*/true, op, + expected_qual | EPT_VLT_LADDR_VLD, gpa); + ept_untwiddle(gpa, /*level=*/1, orig_epte); + do_ept_access_op(op); + } else { + do_ept_access_op(op); + ept_untwiddle(gpa, /*level=*/1, orig_epte); + } + + TEST_ASSERT(*ptep & PT_ACCESSED_MASK); + if ((pte_ad & PT_DIRTY_MASK) || op == OP_WRITE) + TEST_ASSERT(*ptep & PT_DIRTY_MASK); + + skip_exit_vmcall(); +} + +static void ept_access_allowed_paddr(unsigned long ept_access, + unsigned long pte_ad, + enum ept_access_op op) +{ + ept_access_paddr(ept_access, pte_ad, op, /*expect_violation=*/false, + /*expected_qual=*/-1); +} + +static void ept_access_violation_paddr(unsigned long ept_access, + unsigned long pte_ad, + enum ept_access_op op, + u64 expected_qual) +{ + ept_access_paddr(ept_access, pte_ad, op, /*expect_violation=*/true, + expected_qual); +} + + +static void ept_allowed_at_level_mkhuge(bool mkhuge, int level, + unsigned long clear, + unsigned long set, + enum ept_access_op op) +{ + struct ept_access_test_data *data = &ept_access_test_data; + unsigned long orig_pte; + + orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set); + + /* No violation. Should proceed to vmcall. */ + do_ept_access_op(op); + skip_exit_vmcall(); + + ept_untwiddle(data->gpa, level, orig_pte); +} + +static void ept_allowed_at_level(int level, unsigned long clear, + unsigned long set, enum ept_access_op op) +{ + ept_allowed_at_level_mkhuge(false, level, clear, set, op); + if (ept_huge_pages_supported(level)) + ept_allowed_at_level_mkhuge(true, level, clear, set, op); +} + +static void ept_allowed(unsigned long clear, unsigned long set, + enum ept_access_op op) +{ + ept_allowed_at_level(1, clear, set, op); + ept_allowed_at_level(2, clear, set, op); + ept_allowed_at_level(3, clear, set, op); + ept_allowed_at_level(4, clear, set, op); +} + +static void ept_ignored_bit(int bit) +{ + /* Set the bit. */ + ept_allowed(0, 1ul << bit, OP_READ); + ept_allowed(0, 1ul << bit, OP_WRITE); + ept_allowed(0, 1ul << bit, OP_EXEC); + + /* Clear the bit. */ + ept_allowed(1ul << bit, 0, OP_READ); + ept_allowed(1ul << bit, 0, OP_WRITE); + ept_allowed(1ul << bit, 0, OP_EXEC); +} + +static void ept_access_allowed(unsigned long access, enum ept_access_op op) +{ + ept_allowed(EPT_PRESENT, access, op); +} + + +static void ept_misconfig_at_level_mkhuge_op(bool mkhuge, int level, + unsigned long clear, + unsigned long set, + enum ept_access_op op) +{ + struct ept_access_test_data *data = &ept_access_test_data; + unsigned long orig_pte; + + orig_pte = ept_twiddle(data->gpa, mkhuge, level, clear, set); + + do_ept_access_op(op); + assert_exit_reason(VMX_EPT_MISCONFIG); + + /* Intel 27.2.1, "For all other VM exits, this field is cleared." */ + #if 0 + /* broken: */ + TEST_EXPECT_EQ_MSG(vmcs_read(EXI_QUALIFICATION), 0); + #endif + #if 0 + /* + * broken: + * According to description of exit qual for EPT violation, + * EPT_VLT_LADDR_VLD indicates if GUEST_LINEAR_ADDRESS is valid. + * However, I can't find anything that says GUEST_LINEAR_ADDRESS ought + * to be set for msiconfig. + */ + TEST_EXPECT_EQ(vmcs_read(GUEST_LINEAR_ADDRESS), + (unsigned long) ( + op == OP_EXEC ? data->gva + 1 : data->gva)); + #endif + + /* Fix the violation and resume the op loop. */ + ept_untwiddle(data->gpa, level, orig_pte); + enter_guest(); + skip_exit_vmcall(); +} + +static void ept_misconfig_at_level_mkhuge(bool mkhuge, int level, + unsigned long clear, + unsigned long set) +{ + /* The op shouldn't matter (read, write, exec), so try them all! */ + ept_misconfig_at_level_mkhuge_op(mkhuge, level, clear, set, OP_READ); + ept_misconfig_at_level_mkhuge_op(mkhuge, level, clear, set, OP_WRITE); + ept_misconfig_at_level_mkhuge_op(mkhuge, level, clear, set, OP_EXEC); +} + +static void ept_misconfig_at_level(int level, unsigned long clear, + unsigned long set) +{ + ept_misconfig_at_level_mkhuge(false, level, clear, set); + if (ept_huge_pages_supported(level)) + ept_misconfig_at_level_mkhuge(true, level, clear, set); +} + +static void ept_misconfig(unsigned long clear, unsigned long set) +{ + ept_misconfig_at_level(1, clear, set); + ept_misconfig_at_level(2, clear, set); + ept_misconfig_at_level(3, clear, set); + ept_misconfig_at_level(4, clear, set); +} + +static void ept_access_misconfig(unsigned long access) +{ + ept_misconfig(EPT_PRESENT, access); +} + +static void ept_reserved_bit_at_level_nohuge(int level, int bit) +{ + /* Setting the bit causes a misconfig. */ + ept_misconfig_at_level_mkhuge(false, level, 0, 1ul << bit); + + /* Making the entry non-present turns reserved bits into ignored. */ + ept_violation_at_level(level, EPT_PRESENT, 1ul << bit, OP_READ, + EPT_VLT_RD | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR); +} + +static void ept_reserved_bit_at_level_huge(int level, int bit) +{ + /* Setting the bit causes a misconfig. */ + ept_misconfig_at_level_mkhuge(true, level, 0, 1ul << bit); + + /* Making the entry non-present turns reserved bits into ignored. */ + ept_violation_at_level(level, EPT_PRESENT, 1ul << bit, OP_READ, + EPT_VLT_RD | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR); +} + +static void ept_reserved_bit_at_level(int level, int bit) +{ + /* Setting the bit causes a misconfig. */ + ept_misconfig_at_level(level, 0, 1ul << bit); + + /* Making the entry non-present turns reserved bits into ignored. */ + ept_violation_at_level(level, EPT_PRESENT, 1ul << bit, OP_READ, + EPT_VLT_RD | EPT_VLT_LADDR_VLD | EPT_VLT_PADDR); +} + +static void ept_reserved_bit(int bit) +{ + ept_reserved_bit_at_level(1, bit); + ept_reserved_bit_at_level(2, bit); + ept_reserved_bit_at_level(3, bit); + ept_reserved_bit_at_level(4, bit); +} + +#define PAGE_2M_ORDER 9 +#define PAGE_1G_ORDER 18 + +static void *get_1g_page(void) +{ + static void *alloc; + + if (!alloc) + alloc = alloc_pages(PAGE_1G_ORDER); + return alloc; +} + +static void ept_access_test_teardown(void *unused) +{ + /* Exit the guest cleanly. */ + do_ept_access_op(OP_EXIT); +} + +static void ept_access_test_guest(void) +{ + struct ept_access_test_data *data = &ept_access_test_data; + int (*code)(void) = (int (*)(void)) &data->gva[1]; + + while (true) { + switch (data->op) { + case OP_READ: + TEST_ASSERT_EQ(*data->gva, MAGIC_VAL_1); + break; + case OP_WRITE: + *data->gva = MAGIC_VAL_2; + TEST_ASSERT_EQ(*data->gva, MAGIC_VAL_2); + *data->gva = MAGIC_VAL_1; + break; + case OP_EXEC: + TEST_ASSERT_EQ(42, code()); + break; + case OP_FLUSH_TLB: + write_cr3(read_cr3()); + break; + case OP_EXIT: + return; + default: + TEST_ASSERT_MSG(false, "Unknown op %d", data->op); + } + vmcall(); + } +} + +static void ept_access_test_setup(void) +{ + struct ept_access_test_data *data = &ept_access_test_data; + unsigned long npages = 1ul << PAGE_1G_ORDER; + unsigned long size = npages * PAGE_SIZE; + unsigned long *page_table = current_page_table(); + unsigned long pte; + + if (setup_ept(false)) + test_skip("EPT not supported"); + + test_set_guest(ept_access_test_guest); + test_add_teardown(ept_access_test_teardown, NULL); + + data->hva = get_1g_page(); + TEST_ASSERT(data->hva); + data->hpa = virt_to_phys(data->hva); + + data->gpa = 1ul << 40; + data->gva = (void *) ALIGN((unsigned long) alloc_vpages(npages * 2), + size); + TEST_ASSERT(!any_present_pages(page_table, data->gva, size)); + install_pages(page_table, data->gpa, size, data->gva); + + /* + * Make sure nothing's mapped here so the tests that screw with the + * pml4 entry don't inadvertently break something. + */ + TEST_ASSERT(get_ept_pte(pml4, data->gpa, 4, &pte) && pte == 0); + TEST_ASSERT(get_ept_pte(pml4, data->gpa + size - 1, 4, &pte) && pte == 0); + install_ept(pml4, data->hpa, data->gpa, EPT_PRESENT); + + data->hva[0] = MAGIC_VAL_1; + memcpy(&data->hva[1], &ret42_start, &ret42_end - &ret42_start); +} + +static void ept_access_test_not_present(void) +{ + ept_access_test_setup(); + /* --- */ + ept_access_violation(0, OP_READ, EPT_VLT_RD); + ept_access_violation(0, OP_WRITE, EPT_VLT_WR); + ept_access_violation(0, OP_EXEC, EPT_VLT_FETCH); +} + +static void ept_access_test_read_only(void) +{ + ept_access_test_setup(); + + /* r-- */ + ept_access_allowed(EPT_RA, OP_READ); + ept_access_violation(EPT_RA, OP_WRITE, EPT_VLT_WR | EPT_VLT_PERM_RD); + ept_access_violation(EPT_RA, OP_EXEC, EPT_VLT_FETCH | EPT_VLT_PERM_RD); +} + +static void ept_access_test_write_only(void) +{ + ept_access_test_setup(); + /* -w- */ + ept_access_misconfig(EPT_WA); +} + +static void ept_access_test_read_write(void) +{ + ept_access_test_setup(); + /* rw- */ + ept_access_allowed(EPT_RA | EPT_WA, OP_READ); + ept_access_allowed(EPT_RA | EPT_WA, OP_WRITE); + ept_access_violation(EPT_RA | EPT_WA, OP_EXEC, + EPT_VLT_FETCH | EPT_VLT_PERM_RD | EPT_VLT_PERM_WR); +} + + +static void ept_access_test_execute_only(void) +{ + ept_access_test_setup(); + /* --x */ + if (ept_execute_only_supported()) { + ept_access_violation(EPT_EA, OP_READ, + EPT_VLT_RD | EPT_VLT_PERM_EX); + ept_access_violation(EPT_EA, OP_WRITE, + EPT_VLT_WR | EPT_VLT_PERM_EX); + ept_access_allowed(EPT_EA, OP_EXEC); + } else { + ept_access_misconfig(EPT_EA); + } +} + +static void ept_access_test_read_execute(void) +{ + ept_access_test_setup(); + /* r-x */ + ept_access_allowed(EPT_RA | EPT_EA, OP_READ); + ept_access_violation(EPT_RA | EPT_EA, OP_WRITE, + EPT_VLT_WR | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX); + ept_access_allowed(EPT_RA | EPT_EA, OP_EXEC); +} + +static void ept_access_test_write_execute(void) +{ + ept_access_test_setup(); + /* -wx */ + ept_access_misconfig(EPT_WA | EPT_EA); +} + +static void ept_access_test_read_write_execute(void) +{ + ept_access_test_setup(); + /* rwx */ + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_READ); + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_WRITE); + ept_access_allowed(EPT_RA | EPT_WA | EPT_EA, OP_EXEC); +} + +static void ept_access_test_reserved_bits(void) +{ + int i; + int maxphyaddr; + + ept_access_test_setup(); + + /* Reserved bits above maxphyaddr. */ + maxphyaddr = cpuid_maxphyaddr(); + for (i = maxphyaddr; i <= 51; i++) { + report_prefix_pushf("reserved_bit=%d", i); + ept_reserved_bit(i); + report_prefix_pop(); + } + + /* Level-specific reserved bits. */ + ept_reserved_bit_at_level_nohuge(2, 3); + ept_reserved_bit_at_level_nohuge(2, 4); + ept_reserved_bit_at_level_nohuge(2, 5); + ept_reserved_bit_at_level_nohuge(2, 6); + /* 2M alignment. */ + for (i = 12; i < 20; i++) { + report_prefix_pushf("reserved_bit=%d", i); + ept_reserved_bit_at_level_huge(2, i); + report_prefix_pop(); + } + ept_reserved_bit_at_level_nohuge(3, 3); + ept_reserved_bit_at_level_nohuge(3, 4); + ept_reserved_bit_at_level_nohuge(3, 5); + ept_reserved_bit_at_level_nohuge(3, 6); + /* 1G alignment. */ + for (i = 12; i < 29; i++) { + report_prefix_pushf("reserved_bit=%d", i); + ept_reserved_bit_at_level_huge(3, i); + report_prefix_pop(); + } + ept_reserved_bit_at_level(4, 3); + ept_reserved_bit_at_level(4, 4); + ept_reserved_bit_at_level(4, 5); + ept_reserved_bit_at_level(4, 6); + ept_reserved_bit_at_level(4, 7); +} + +static void ept_access_test_ignored_bits(void) +{ + ept_access_test_setup(); + /* + * Bits ignored at every level. Bits 8 and 9 (A and D) are ignored as + * far as translation is concerned even if AD bits are enabled in the + * EPTP. Bit 63 is ignored because "EPT-violation #VE" VM-execution + * control is 0. + */ + ept_ignored_bit(8); + ept_ignored_bit(9); + ept_ignored_bit(10); + ept_ignored_bit(11); + ept_ignored_bit(52); + ept_ignored_bit(53); + ept_ignored_bit(54); + ept_ignored_bit(55); + ept_ignored_bit(56); + ept_ignored_bit(57); + ept_ignored_bit(58); + ept_ignored_bit(59); + ept_ignored_bit(60); + ept_ignored_bit(61); + ept_ignored_bit(62); + ept_ignored_bit(63); +} + +static void ept_access_test_paddr_not_present_ad_disabled(void) +{ + ept_access_test_setup(); + ept_disable_ad_bits(); + + ept_access_violation_paddr(0, PT_AD_MASK, OP_READ, EPT_VLT_RD); + ept_access_violation_paddr(0, PT_AD_MASK, OP_WRITE, EPT_VLT_RD); + ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC, EPT_VLT_RD); +} + +static void ept_access_test_paddr_not_present_ad_enabled(void) +{ + u64 qual = EPT_VLT_RD | EPT_VLT_WR; + + ept_access_test_setup(); + ept_enable_ad_bits_or_skip_test(); + + ept_access_violation_paddr(0, PT_AD_MASK, OP_READ, qual); + ept_access_violation_paddr(0, PT_AD_MASK, OP_WRITE, qual); + ept_access_violation_paddr(0, PT_AD_MASK, OP_EXEC, qual); +} + +static void ept_access_test_paddr_read_only_ad_disabled(void) +{ + /* + * When EPT AD bits are disabled, all accesses to guest paging + * structures are reported separately as a read and (after + * translation of the GPA to host physical address) a read+write + * if the A/D bits have to be set. + */ + u64 qual = EPT_VLT_WR | EPT_VLT_RD | EPT_VLT_PERM_RD; + + ept_access_test_setup(); + ept_disable_ad_bits(); + + /* Can't update A bit, so all accesses fail. */ + ept_access_violation_paddr(EPT_RA, 0, OP_READ, qual); + ept_access_violation_paddr(EPT_RA, 0, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA, 0, OP_EXEC, qual); + /* AD bits disabled, so only writes try to update the D bit. */ + ept_access_allowed_paddr(EPT_RA, PT_ACCESSED_MASK, OP_READ); + ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_WRITE, qual); + ept_access_allowed_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC); + /* Both A and D already set, so read-only is OK. */ + ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_READ); + ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_WRITE); + ept_access_allowed_paddr(EPT_RA, PT_AD_MASK, OP_EXEC); +} + +static void ept_access_test_paddr_read_only_ad_enabled(void) +{ + /* + * When EPT AD bits are enabled, all accesses to guest paging + * structures are considered writes as far as EPT translation + * is concerned. + */ + u64 qual = EPT_VLT_WR | EPT_VLT_RD | EPT_VLT_PERM_RD; + + ept_access_test_setup(); + ept_enable_ad_bits_or_skip_test(); + + ept_access_violation_paddr(EPT_RA, 0, OP_READ, qual); + ept_access_violation_paddr(EPT_RA, 0, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA, 0, OP_EXEC, qual); + ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_READ, qual); + ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA, PT_ACCESSED_MASK, OP_EXEC, qual); + ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_READ, qual); + ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA, PT_AD_MASK, OP_EXEC, qual); +} + +static void ept_access_test_paddr_read_write(void) +{ + ept_access_test_setup(); + /* Read-write access to paging structure. */ + ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_READ); + ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_WRITE); + ept_access_allowed_paddr(EPT_RA | EPT_WA, 0, OP_EXEC); +} + +static void ept_access_test_paddr_read_write_execute(void) +{ + ept_access_test_setup(); + /* RWX access to paging structure. */ + ept_access_allowed_paddr(EPT_PRESENT, 0, OP_READ); + ept_access_allowed_paddr(EPT_PRESENT, 0, OP_WRITE); + ept_access_allowed_paddr(EPT_PRESENT, 0, OP_EXEC); +} + +static void ept_access_test_paddr_read_execute_ad_disabled(void) +{ + /* + * When EPT AD bits are disabled, all accesses to guest paging + * structures are reported separately as a read and (after + * translation of the GPA to host physical address) a read+write + * if the A/D bits have to be set. + */ + u64 qual = EPT_VLT_WR | EPT_VLT_RD | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX; + + ept_access_test_setup(); + ept_disable_ad_bits(); + + /* Can't update A bit, so all accesses fail. */ + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_READ, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC, qual); + /* AD bits disabled, so only writes try to update the D bit. */ + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_READ); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_WRITE, qual); + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_EXEC); + /* Both A and D already set, so read-only is OK. */ + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_READ); + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_WRITE); + ept_access_allowed_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_EXEC); +} + +static void ept_access_test_paddr_read_execute_ad_enabled(void) +{ + /* + * When EPT AD bits are enabled, all accesses to guest paging + * structures are considered writes as far as EPT translation + * is concerned. + */ + u64 qual = EPT_VLT_WR | EPT_VLT_RD | EPT_VLT_PERM_RD | EPT_VLT_PERM_EX; + + ept_access_test_setup(); + ept_enable_ad_bits_or_skip_test(); + + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_READ, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, 0, OP_EXEC, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_READ, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_ACCESSED_MASK, OP_EXEC, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_READ, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_WRITE, qual); + ept_access_violation_paddr(EPT_RA | EPT_EA, PT_AD_MASK, OP_EXEC, qual); +} + +static void ept_access_test_paddr_not_present_page_fault(void) +{ + ept_access_test_setup(); + /* + * TODO: test no EPT violation as long as guest PF occurs. e.g., GPA is + * page is read-only in EPT but GVA is also mapped read only in PT. + * Thus guest page fault before host takes EPT violation for trying to + * update A bit. + */ +} + +static void ept_access_test_force_2m_page(void) +{ + ept_access_test_setup(); + + TEST_ASSERT_EQ(ept_2m_supported(), true); + ept_allowed_at_level_mkhuge(true, 2, 0, 0, OP_READ); + ept_violation_at_level_mkhuge(true, 2, EPT_PRESENT, EPT_RA, OP_WRITE, + EPT_VLT_WR | EPT_VLT_PERM_RD | + EPT_VLT_LADDR_VLD | EPT_VLT_PADDR); + ept_misconfig_at_level_mkhuge(true, 2, EPT_PRESENT, EPT_WA); +} + +static bool invvpid_valid(u64 type, u64 vpid, u64 gla) +{ + u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP); + + TEST_ASSERT(msr & VPID_CAP_INVVPID); + + if (type < INVVPID_ADDR || type > INVVPID_CONTEXT_LOCAL) + return false; + + if (!(msr & (1ull << (type + VPID_CAP_INVVPID_TYPES_SHIFT)))) + return false; + + if (vpid >> 16) + return false; + + if (type != INVVPID_ALL && !vpid) + return false; + + if (type == INVVPID_ADDR && !is_canonical(gla)) + return false; + + return true; +} + +static void try_invvpid(u64 type, u64 vpid, u64 gla) +{ + int rc; + bool valid = invvpid_valid(type, vpid, gla); + u64 expected = valid ? VMXERR_UNSUPPORTED_VMCS_COMPONENT + : VMXERR_INVALID_OPERAND_TO_INVEPT_INVVPID; + /* + * Set VMX_INST_ERROR to VMXERR_UNVALID_VMCS_COMPONENT, so + * that we can tell if it is updated by INVVPID. + */ + vmcs_read(~0); + rc = invvpid(type, vpid, gla); + report("INVVPID type %ld VPID %lx GLA %lx %s", + !rc == valid, type, vpid, gla, + valid ? "passes" : "fails"); + report("After %s INVVPID, VMX_INST_ERR is %ld (actual %ld)", + vmcs_read(VMX_INST_ERROR) == expected, + rc ? "failed" : "successful", + expected, vmcs_read(VMX_INST_ERROR)); +} + +static void ds_invvpid(void *data) +{ + u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP); + u64 type = ffs(msr >> VPID_CAP_INVVPID_TYPES_SHIFT) - 1; + + TEST_ASSERT(type >= INVVPID_ADDR && type <= INVVPID_CONTEXT_LOCAL); + asm volatile("invvpid %0, %1" + : + : "m"(*(struct invvpid_operand *)data), + "r"(type)); +} + +/* + * The SS override is ignored in 64-bit mode, so we use an addressing + * mode with %rsp as the base register to generate an implicit SS + * reference. + */ +static void ss_invvpid(void *data) +{ + u64 msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP); + u64 type = ffs(msr >> VPID_CAP_INVVPID_TYPES_SHIFT) - 1; + + TEST_ASSERT(type >= INVVPID_ADDR && type <= INVVPID_CONTEXT_LOCAL); + asm volatile("sub %%rsp,%0; invvpid (%%rsp,%0,1), %1" + : "+r"(data) + : "r"(type)); +} + +static void invvpid_test_gp(void) +{ + bool fault; + + fault = test_for_exception(GP_VECTOR, &ds_invvpid, + (void *)NONCANONICAL); + report("INVVPID with non-canonical DS operand raises #GP", fault); +} + +static void invvpid_test_ss(void) +{ + bool fault; + + fault = test_for_exception(SS_VECTOR, &ss_invvpid, + (void *)NONCANONICAL); + report("INVVPID with non-canonical SS operand raises #SS", fault); +} + +static void invvpid_test_pf(void) +{ + void *vpage = alloc_vpage(); + bool fault; + + fault = test_for_exception(PF_VECTOR, &ds_invvpid, vpage); + report("INVVPID with unmapped operand raises #PF", fault); +} + +static void try_compat_invvpid(void *unused) +{ + struct far_pointer32 fp = { + .offset = (uintptr_t)&&invvpid, + .selector = KERNEL_CS32, + }; + register uintptr_t rsp asm("rsp"); + + TEST_ASSERT_MSG(fp.offset == (uintptr_t)&&invvpid, + "Code address too high."); + TEST_ASSERT_MSG(rsp == (u32)rsp, "Stack address too high."); + + asm goto ("lcall *%0" : : "m" (fp) : "rax" : invvpid); + return; +invvpid: + asm volatile (".code32;" + "invvpid (%eax), %eax;" + "lret;" + ".code64"); + __builtin_unreachable(); +} + +static void invvpid_test_compatibility_mode(void) +{ + bool fault; + + fault = test_for_exception(UD_VECTOR, &try_compat_invvpid, NULL); + report("Compatibility mode INVVPID raises #UD", fault); +} + +static void invvpid_test_not_in_vmx_operation(void) +{ + bool fault; + + TEST_ASSERT(!vmx_off()); + fault = test_for_exception(UD_VECTOR, &ds_invvpid, NULL); + report("INVVPID outside of VMX operation raises #UD", fault); + TEST_ASSERT(!vmx_on()); +} + +/* + * This does not test real-address mode, virtual-8086 mode, protected mode, + * or CPL > 0. + */ +static void invvpid_test_v2(void) +{ + u64 msr; + int i; + unsigned types = 0; + unsigned type; + + if (!(ctrl_cpu_rev[0].clr & CPU_SECONDARY) || + !(ctrl_cpu_rev[1].clr & CPU_VPID)) + test_skip("VPID not supported"); + + msr = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP); + + if (!(msr & VPID_CAP_INVVPID)) + test_skip("INVVPID not supported.\n"); + + if (msr & VPID_CAP_INVVPID_ADDR) + types |= 1u << INVVPID_ADDR; + if (msr & VPID_CAP_INVVPID_CXTGLB) + types |= 1u << INVVPID_CONTEXT_GLOBAL; + if (msr & VPID_CAP_INVVPID_ALL) + types |= 1u << INVVPID_ALL; + if (msr & VPID_CAP_INVVPID_CXTLOC) + types |= 1u << INVVPID_CONTEXT_LOCAL; + + if (!types) + test_skip("No INVVPID types supported.\n"); + + for (i = -127; i < 128; i++) + try_invvpid(i, 0xffff, 0); + + /* + * VPID must not be more than 16 bits. + */ + for (i = 0; i < 64; i++) + for (type = 0; type < 4; type++) + if (types & (1u << type)) + try_invvpid(type, 1ul << i, 0); + + /* + * VPID must not be zero, except for "all contexts." + */ + for (type = 0; type < 4; type++) + if (types & (1u << type)) + try_invvpid(type, 0, 0); + + /* + * The gla operand is only validated for single-address INVVPID. + */ + if (types & (1u << INVVPID_ADDR)) + try_invvpid(INVVPID_ADDR, 0xffff, NONCANONICAL); + + invvpid_test_gp(); + invvpid_test_ss(); + invvpid_test_pf(); + invvpid_test_compatibility_mode(); + invvpid_test_not_in_vmx_operation(); +} + +/* + * Test for early VMLAUNCH failure. Returns true if VMLAUNCH makes it + * at least as far as the guest-state checks. Returns false if the + * VMLAUNCH fails early and execution falls through to the next + * instruction. + */ +static bool vmlaunch_succeeds(void) +{ + /* + * Indirectly set VMX_INST_ERR to 12 ("VMREAD/VMWRITE from/to + * unsupported VMCS component"). The caller can then check + * to see if a failed VM-entry sets VMX_INST_ERR as expected. + */ + vmcs_write(~0u, 0); + + vmcs_write(HOST_RIP, (uintptr_t)&&success); + __asm__ __volatile__ goto ("vmwrite %%rsp, %0; vmlaunch" + : + : "r" ((u64)HOST_RSP) + : "cc", "memory" + : success); + return false; +success: + TEST_ASSERT(vmcs_read(EXI_REASON) == + (VMX_FAIL_STATE | VMX_ENTRY_FAILURE)); + return true; +} + +/* + * Try to launch the current VMCS. + */ +static void test_vmx_controls(bool controls_valid) +{ + bool success = vmlaunch_succeeds(); + u32 vmx_inst_err; + + report("vmlaunch %s", success == controls_valid, + controls_valid ? "succeeds" : "fails"); + if (!controls_valid) { + vmx_inst_err = vmcs_read(VMX_INST_ERROR); + report("VMX inst error is %d (actual %d)", + vmx_inst_err == VMXERR_ENTRY_INVALID_CONTROL_FIELD, + VMXERR_ENTRY_INVALID_CONTROL_FIELD, vmx_inst_err); + } +} + +/* + * Test a particular address setting for a physical page reference in + * the VMCS. + */ +static void test_vmcs_page_addr(const char *name, + enum Encoding encoding, + bool ignored, + u64 addr) +{ + report_prefix_pushf("%s = %lx", name, addr); + vmcs_write(encoding, addr); + test_vmx_controls(ignored || (IS_ALIGNED(addr, PAGE_SIZE) && + addr < (1ul << cpuid_maxphyaddr()))); + report_prefix_pop(); +} + +/* + * Test interesting values for a physical page reference in the VMCS. + */ +static void test_vmcs_page_values(const char *name, + enum Encoding encoding, + bool ignored) +{ + unsigned i; + u64 orig_val = vmcs_read(encoding); + + for (i = 0; i < 64; i++) + test_vmcs_page_addr(name, encoding, ignored, 1ul << i); + + test_vmcs_page_addr(name, encoding, ignored, PAGE_SIZE - 1); + test_vmcs_page_addr(name, encoding, ignored, PAGE_SIZE); + test_vmcs_page_addr(name, encoding, ignored, + (1ul << cpuid_maxphyaddr()) - PAGE_SIZE); + test_vmcs_page_addr(name, encoding, ignored, -1ul); + + vmcs_write(encoding, orig_val); +} + +/* + * Test a physical page reference in the VMCS, when the corresponding + * feature is enabled and when the corresponding feature is disabled. + */ +static void test_vmcs_page_reference(u32 control_bit, enum Encoding field, + const char *field_name, + const char *control_name) +{ + u32 primary = vmcs_read(CPU_EXEC_CTRL0); + u64 page_addr; + + if (!(ctrl_cpu_rev[0].clr & control_bit)) + return; + + page_addr = vmcs_read(field); + + report_prefix_pushf("%s enabled", control_name); + vmcs_write(CPU_EXEC_CTRL0, primary | control_bit); + test_vmcs_page_values(field_name, field, false); + report_prefix_pop(); + + report_prefix_pushf("%s disabled", control_name); + vmcs_write(CPU_EXEC_CTRL0, primary & ~control_bit); + test_vmcs_page_values(field_name, field, true); + report_prefix_pop(); + + vmcs_write(field, page_addr); + vmcs_write(CPU_EXEC_CTRL0, primary); +} + +/* + * If the "use I/O bitmaps" VM-execution control is 1, bits 11:0 of + * each I/O-bitmap address must be 0. Neither address should set any + * bits beyond the processor's physical-address width. + * [Intel SDM] + */ +static void test_io_bitmaps(void) +{ + test_vmcs_page_reference(CPU_IO_BITMAP, IO_BITMAP_A, + "I/O bitmap A", "Use I/O bitmaps"); + test_vmcs_page_reference(CPU_IO_BITMAP, IO_BITMAP_B, + "I/O bitmap B", "Use I/O bitmaps"); +} + +/* + * If the "use MSR bitmaps" VM-execution control is 1, bits 11:0 of + * the MSR-bitmap address must be 0. The address should not set any + * bits beyond the processor's physical-address width. + * [Intel SDM] + */ +static void test_msr_bitmap(void) +{ + test_vmcs_page_reference(CPU_MSR_BITMAP, MSR_BITMAP, + "MSR bitmap", "Use MSR bitmaps"); +} + +static void vmx_controls_test(void) +{ + /* + * Bit 1 of the guest's RFLAGS must be 1, or VM-entry will + * fail due to invalid guest state, should we make it that + * far. + */ + vmcs_write(GUEST_RFLAGS, 0); + + test_io_bitmaps(); + test_msr_bitmap(); +} + +static bool valid_vmcs_for_vmentry(void) +{ + struct vmcs *current_vmcs = NULL; + + if (vmcs_save(¤t_vmcs)) + return false; + + return current_vmcs && !(current_vmcs->revision_id >> 31); +} + +static void try_vmentry_in_movss_shadow(void) +{ + u32 vm_inst_err; + u32 flags; + bool early_failure = false; + u32 expected_flags = X86_EFLAGS_FIXED; + bool valid_vmcs = valid_vmcs_for_vmentry(); + + expected_flags |= valid_vmcs ? X86_EFLAGS_ZF : X86_EFLAGS_CF; + + /* + * Indirectly set VM_INST_ERR to 12 ("VMREAD/VMWRITE from/to + * unsupported VMCS component"). + */ + vmcs_write(~0u, 0); + + __asm__ __volatile__ ("mov %[host_rsp], %%edx;" + "vmwrite %%rsp, %%rdx;" + "mov 0f, %%rax;" + "mov %[host_rip], %%edx;" + "vmwrite %%rax, %%rdx;" + "mov $-1, %%ah;" + "sahf;" + "mov %%ss, %%ax;" + "mov %%ax, %%ss;" + "vmlaunch;" + "mov $1, %[early_failure];" + "0: lahf;" + "movzbl %%ah, %[flags]" + : [early_failure] "+r" (early_failure), + [flags] "=&a" (flags) + : [host_rsp] "i" (HOST_RSP), + [host_rip] "i" (HOST_RIP) + : "rdx", "cc", "memory"); + vm_inst_err = vmcs_read(VMX_INST_ERROR); + + report("Early VM-entry failure", early_failure); + report("RFLAGS[8:0] is %x (actual %x)", flags == expected_flags, + expected_flags, flags); + if (valid_vmcs) + report("VM-instruction error is %d (actual %d)", + vm_inst_err == VMXERR_ENTRY_EVENTS_BLOCKED_BY_MOV_SS, + VMXERR_ENTRY_EVENTS_BLOCKED_BY_MOV_SS, vm_inst_err); +} + +static void vmentry_movss_shadow_test(void) +{ + struct vmcs *orig_vmcs; + + TEST_ASSERT(!vmcs_save(&orig_vmcs)); + + /* + * Set the launched flag on the current VMCS to verify the correct + * error priority, below. + */ + test_set_guest(v2_null_test_guest); + enter_guest(); + + /* + * With bit 1 of the guest's RFLAGS clear, VM-entry should + * fail due to invalid guest state (if we make it that far). + */ + vmcs_write(GUEST_RFLAGS, 0); + + /* + * "VM entry with events blocked by MOV SS" takes precedence over + * "VMLAUNCH with non-clear VMCS." + */ + report_prefix_push("valid current-VMCS"); + try_vmentry_in_movss_shadow(); + report_prefix_pop(); + + /* + * VMfailInvalid takes precedence over "VM entry with events + * blocked by MOV SS." + */ + TEST_ASSERT(!vmcs_clear(orig_vmcs)); + report_prefix_push("no current-VMCS"); + try_vmentry_in_movss_shadow(); + report_prefix_pop(); + + TEST_ASSERT(!make_vmcs_current(orig_vmcs)); + vmcs_write(GUEST_RFLAGS, X86_EFLAGS_FIXED); +} + +#define TEST(name) { #name, .v2 = name } + /* name/init/guest_main/exit_handler/syscall_handler/guest_regs */ struct vmx_test vmx_tests[] = { { "null", NULL, basic_guest_main, basic_exit_handler, NULL, {0} }, @@ -1832,7 +3406,9 @@ struct vmx_test vmx_tests[] = { NULL, {0} }, { "instruction intercept", insn_intercept_init, insn_intercept_main, insn_intercept_exit_handler, NULL, {0} }, - { "EPT framework", ept_init, ept_main, ept_exit_handler, NULL, {0} }, + { "EPT A/D disabled", ept_init, ept_main, ept_exit_handler, NULL, {0} }, + { "EPT A/D enabled", eptad_init, eptad_main, eptad_exit_handler, NULL, {0} }, + { "PML", pml_init, pml_main, pml_exit_handler, NULL, {0} }, { "VPID", vpid_init, vpid_main, vpid_exit_handler, NULL, {0} }, { "interrupt", interrupt_init, interrupt_main, interrupt_exit_handler, NULL, {0} }, @@ -1845,5 +3421,38 @@ struct vmx_test vmx_tests[] = { disable_rdtscp_exit_handler, NULL, {0} }, { "int3", int3_init, int3_guest_main, int3_exit_handler, NULL, {0} }, { "into", into_init, into_guest_main, into_exit_handler, NULL, {0} }, + { "exit_monitor_from_l2_test", NULL, exit_monitor_from_l2_main, + exit_monitor_from_l2_handler, NULL, {0} }, + /* Basic V2 tests. */ + TEST(v2_null_test), + TEST(v2_multiple_entries_test), + TEST(fixture_test_case1), + TEST(fixture_test_case2), + /* EPT access tests. */ + TEST(ept_access_test_not_present), + TEST(ept_access_test_read_only), + TEST(ept_access_test_write_only), + TEST(ept_access_test_read_write), + TEST(ept_access_test_execute_only), + TEST(ept_access_test_read_execute), + TEST(ept_access_test_write_execute), + TEST(ept_access_test_read_write_execute), + TEST(ept_access_test_reserved_bits), + TEST(ept_access_test_ignored_bits), + TEST(ept_access_test_paddr_not_present_ad_disabled), + TEST(ept_access_test_paddr_not_present_ad_enabled), + TEST(ept_access_test_paddr_read_only_ad_disabled), + TEST(ept_access_test_paddr_read_only_ad_enabled), + TEST(ept_access_test_paddr_read_write), + TEST(ept_access_test_paddr_read_write_execute), + TEST(ept_access_test_paddr_read_execute_ad_disabled), + TEST(ept_access_test_paddr_read_execute_ad_enabled), + TEST(ept_access_test_paddr_not_present_page_fault), + TEST(ept_access_test_force_2m_page), + /* Opcode tests. */ + TEST(invvpid_test_v2), + /* VM-entry tests */ + TEST(vmx_controls_test), + TEST(vmentry_movss_shadow_test), { NULL, NULL, NULL, NULL, NULL, {0} }, }; diff --git a/tests/kvm-unit-tests/x86/xsave.c b/tests/kvm-unit-tests/x86/xsave.c index 52142d2c..2d8ca7c2 100644 --- a/tests/kvm-unit-tests/x86/xsave.c +++ b/tests/kvm-unit-tests/x86/xsave.c @@ -75,7 +75,7 @@ void test_xsave(void) printf("Legal instruction testing:\n"); supported_xcr0 = get_supported_xcr0(); - printf("Supported XCR0 bits: 0x%lx\n", supported_xcr0); + printf("Supported XCR0 bits: %#lx\n", supported_xcr0); test_bits = XSTATE_FP | XSTATE_SSE; report("Check minimal XSAVE required bits",