From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50934) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1VWWLt-0001aB-4T for qemu-devel@nongnu.org; Wed, 16 Oct 2013 15:02:23 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1VWWLI-0005IK-CK for qemu-devel@nongnu.org; Wed, 16 Oct 2013 15:01:49 -0400 Received: from mx1.redhat.com ([209.132.183.28]:43617) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1VWWLH-0005I3-BY for qemu-devel@nongnu.org; Wed, 16 Oct 2013 15:01:12 -0400 Date: Wed, 16 Oct 2013 22:03:37 +0300 From: "Michael S. Tsirkin" Message-ID: <1381949935-8692-1-git-send-email-mst@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Subject: [Qemu-devel] [PATCH] import kvm-unittest in QEMU source tree List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: pbonzini@redhat.com, gleb@redhat.com, Anthony Liguori This simply imports kvm-unittest git into qemu source tree. We can next work on making make check run it automatically. Squashed 'kvm-unittest/' content from commit 2bc0e29 git-subtree-dir: kvm-unittest git-subtree-split: 2bc0e29ee4447bebcd3b90053881f59265306adc Signed-off-by: Michael S. Tsirkin --- Gleb, Paolo, any objections to this? I really want a small guest for running ACPI tests during make check, and kvm-unittest seems to fit the bill. Ability to test e.g. PCI with this in-tree would be very benefitial. diff --git a/kvm-unittest/iotable.h b/kvm-unittest/iotable.h new file mode 100644 index 0000000..cb18f23 --- /dev/null +++ b/kvm-unittest/iotable.h @@ -0,0 +1,40 @@ +/* + * Kernel-based Virtual Machine test driver + * + * This test driver provides a simple way of testing kvm, without a full + * device model. + * + * Copyright (C) 2006 Qumranet + * + * Authors: + * + * Avi Kivity + * Yaniv Kamay + * + * This work is licensed under the GNU LGPL license, version 2. + */ + +#include + +#define MAX_IO_TABLE 50 + +typedef int (io_table_handler_t)(void *, int, int, uint64_t, uint64_t *); + +struct io_table_entry +{ + uint64_t start; + uint64_t end; + io_table_handler_t *handler; + void *opaque; +}; + +struct io_table +{ + int nr_entries; + struct io_table_entry entries[MAX_IO_TABLE]; +}; + +struct io_table_entry *io_table_lookup(struct io_table *io_table, + uint64_t addr); +int io_table_register(struct io_table *io_table, uint64_t start, uint64_t size, + io_table_handler_t *handler, void *opaque); diff --git a/kvm-unittest/lib/libcflat.h b/kvm-unittest/lib/libcflat.h new file mode 100644 index 0000000..fadc33d --- /dev/null +++ b/kvm-unittest/lib/libcflat.h @@ -0,0 +1,61 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +#ifndef __LIBCFLAT_H +#define __LIBCFLAT_H + +#include + +typedef unsigned char u8; +typedef signed char s8; +typedef unsigned short u16; +typedef signed short s16; +typedef unsigned u32; +typedef signed s32; +typedef unsigned long ulong; +typedef unsigned long long u64; +typedef signed long long s64; +typedef unsigned long size_t; +typedef _Bool bool; + +#define true 1 +#define false 0 + +extern void exit(int code); +extern void panic(char *fmt, ...); + +extern unsigned long strlen(const char *buf); +extern char *strcat(char *dest, const char *src); +extern int strcmp(const char *a, const char *b); + +extern int printf(const char *fmt, ...); +extern int vsnprintf(char *buf, int size, const char *fmt, va_list va); + +extern void puts(const char *s); + +extern void *memset(void *s, int c, size_t n); +extern void *memcpy(void *dest, const void *src, size_t n); + +extern long atol(const char *ptr); +#define ARRAY_SIZE(_a) (sizeof(_a)/sizeof((_a)[0])) + +#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER) + +#define NULL ((void *)0UL) +#endif diff --git a/kvm-unittest/lib/powerpc/44x/timebase.h b/kvm-unittest/lib/powerpc/44x/timebase.h new file mode 100644 index 0000000..ce85347 --- /dev/null +++ b/kvm-unittest/lib/powerpc/44x/timebase.h @@ -0,0 +1,25 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +#ifndef __TIMEBASE_H__ +#define __TIMEBASE_H__ + +unsigned long long mftb(void); + +#endif /* __TIMEBASE_H__ */ diff --git a/kvm-unittest/lib/x86/apic-defs.h b/kvm-unittest/lib/x86/apic-defs.h new file mode 100644 index 0000000..c061e3d --- /dev/null +++ b/kvm-unittest/lib/x86/apic-defs.h @@ -0,0 +1,133 @@ +#ifndef _ASM_X86_APICDEF_H +#define _ASM_X86_APICDEF_H + +/* + * Constants for various Intel APICs. (local APIC, IOAPIC, etc.) + * + * Alan Cox , 1995. + * Ingo Molnar , 1999, 2000 + */ + +#define APIC_DEFAULT_PHYS_BASE 0xfee00000 + +#define APIC_ID 0x20 + +#define APIC_LVR 0x30 +#define APIC_LVR_MASK 0xFF00FF +#define GET_APIC_VERSION(x) ((x) & 0xFFu) +#define GET_APIC_MAXLVT(x) (((x) >> 16) & 0xFFu) +#ifdef CONFIG_X86_32 +# define APIC_INTEGRATED(x) ((x) & 0xF0u) +#else +# define APIC_INTEGRATED(x) (1) +#endif +#define APIC_XAPIC(x) ((x) >= 0x14) +#define APIC_TASKPRI 0x80 +#define APIC_TPRI_MASK 0xFFu +#define APIC_ARBPRI 0x90 +#define APIC_ARBPRI_MASK 0xFFu +#define APIC_PROCPRI 0xA0 +#define APIC_EOI 0xB0 +#define APIC_EIO_ACK 0x0 +#define APIC_RRR 0xC0 +#define APIC_LDR 0xD0 +#define APIC_LDR_MASK (0xFFu << 24) +#define GET_APIC_LOGICAL_ID(x) (((x) >> 24) & 0xFFu) +#define SET_APIC_LOGICAL_ID(x) (((x) << 24)) +#define APIC_ALL_CPUS 0xFFu +#define APIC_DFR 0xE0 +#define APIC_DFR_CLUSTER 0x0FFFFFFFul +#define APIC_DFR_FLAT 0xFFFFFFFFul +#define APIC_SPIV 0xF0 +#define APIC_SPIV_FOCUS_DISABLED (1 << 9) +#define APIC_SPIV_APIC_ENABLED (1 << 8) +#define APIC_ISR 0x100 +#define APIC_ISR_NR 0x8 /* Number of 32 bit ISR registers. */ +#define APIC_TMR 0x180 +#define APIC_IRR 0x200 +#define APIC_ESR 0x280 +#define APIC_ESR_SEND_CS 0x00001 +#define APIC_ESR_RECV_CS 0x00002 +#define APIC_ESR_SEND_ACC 0x00004 +#define APIC_ESR_RECV_ACC 0x00008 +#define APIC_ESR_SENDILL 0x00020 +#define APIC_ESR_RECVILL 0x00040 +#define APIC_ESR_ILLREGA 0x00080 +#define APIC_ICR 0x300 +#define APIC_DEST_SELF 0x40000 +#define APIC_DEST_ALLINC 0x80000 +#define APIC_DEST_ALLBUT 0xC0000 +#define APIC_ICR_RR_MASK 0x30000 +#define APIC_ICR_RR_INVALID 0x00000 +#define APIC_ICR_RR_INPROG 0x10000 +#define APIC_ICR_RR_VALID 0x20000 +#define APIC_INT_LEVELTRIG 0x08000 +#define APIC_INT_ASSERT 0x04000 +#define APIC_ICR_BUSY 0x01000 +#define APIC_DEST_LOGICAL 0x00800 +#define APIC_DEST_PHYSICAL 0x00000 +#define APIC_DM_FIXED 0x00000 +#define APIC_DM_LOWEST 0x00100 +#define APIC_DM_SMI 0x00200 +#define APIC_DM_REMRD 0x00300 +#define APIC_DM_NMI 0x00400 +#define APIC_DM_INIT 0x00500 +#define APIC_DM_STARTUP 0x00600 +#define APIC_DM_EXTINT 0x00700 +#define APIC_VECTOR_MASK 0x000FF +#define APIC_ICR2 0x310 +#define GET_APIC_DEST_FIELD(x) (((x) >> 24) & 0xFF) +#define SET_APIC_DEST_FIELD(x) ((x) << 24) +#define APIC_LVTT 0x320 +#define APIC_LVTTHMR 0x330 +#define APIC_LVTPC 0x340 +#define APIC_LVT0 0x350 +#define APIC_LVT_TIMER_BASE_MASK (0x3 << 18) +#define GET_APIC_TIMER_BASE(x) (((x) >> 18) & 0x3) +#define SET_APIC_TIMER_BASE(x) (((x) << 18)) +#define APIC_TIMER_BASE_CLKIN 0x0 +#define APIC_TIMER_BASE_TMBASE 0x1 +#define APIC_TIMER_BASE_DIV 0x2 +#define APIC_LVT_TIMER_PERIODIC (1 << 17) +#define APIC_LVT_MASKED (1 << 16) +#define APIC_LVT_LEVEL_TRIGGER (1 << 15) +#define APIC_LVT_REMOTE_IRR (1 << 14) +#define APIC_INPUT_POLARITY (1 << 13) +#define APIC_SEND_PENDING (1 << 12) +#define APIC_MODE_MASK 0x700 +#define GET_APIC_DELIVERY_MODE(x) (((x) >> 8) & 0x7) +#define SET_APIC_DELIVERY_MODE(x, y) (((x) & ~0x700) | ((y) << 8)) +#define APIC_MODE_FIXED 0x0 +#define APIC_MODE_NMI 0x4 +#define APIC_MODE_EXTINT 0x7 +#define APIC_LVT1 0x360 +#define APIC_LVTERR 0x370 +#define APIC_TMICT 0x380 +#define APIC_TMCCT 0x390 +#define APIC_TDCR 0x3E0 +#define APIC_SELF_IPI 0x3F0 +#define APIC_TDR_DIV_TMBASE (1 << 2) +#define APIC_TDR_DIV_1 0xB +#define APIC_TDR_DIV_2 0x0 +#define APIC_TDR_DIV_4 0x1 +#define APIC_TDR_DIV_8 0x2 +#define APIC_TDR_DIV_16 0x3 +#define APIC_TDR_DIV_32 0x8 +#define APIC_TDR_DIV_64 0x9 +#define APIC_TDR_DIV_128 0xA +#define APIC_EILVT0 0x500 +#define APIC_EILVT_NR_AMD_K8 1 /* # of extended interrupts */ +#define APIC_EILVT_NR_AMD_10H 4 +#define APIC_EILVT_LVTOFF(x) (((x) >> 4) & 0xF) +#define APIC_EILVT_MSG_FIX 0x0 +#define APIC_EILVT_MSG_SMI 0x2 +#define APIC_EILVT_MSG_NMI 0x4 +#define APIC_EILVT_MSG_EXT 0x7 +#define APIC_EILVT_MASKED (1 << 16) +#define APIC_EILVT1 0x510 +#define APIC_EILVT2 0x520 +#define APIC_EILVT3 0x530 + +#define APIC_BASE_MSR 0x800 + +#endif /* _ASM_X86_APICDEF_H */ diff --git a/kvm-unittest/lib/x86/apic.h b/kvm-unittest/lib/x86/apic.h new file mode 100644 index 0000000..e325e9a --- /dev/null +++ b/kvm-unittest/lib/x86/apic.h @@ -0,0 +1,34 @@ +#ifndef CFLAT_APIC_H +#define CFLAT_APIC_H + +#include +#include "apic-defs.h" + +typedef struct { + uint8_t vector; + uint8_t delivery_mode:3; + uint8_t dest_mode:1; + uint8_t delivery_status:1; + uint8_t polarity:1; + uint8_t remote_irr:1; + uint8_t trig_mode:1; + uint8_t mask:1; + uint8_t reserve:7; + uint8_t reserved[4]; + uint8_t dest_id; +} ioapic_redir_entry_t; + +void mask_pic_interrupts(void); + +void ioapic_write_redir(unsigned line, ioapic_redir_entry_t e); +void ioapic_write_reg(unsigned reg, uint32_t value); + +void enable_apic(void); +uint32_t apic_read(unsigned reg); +void apic_write(unsigned reg, uint32_t val); +void apic_icr_write(uint32_t val, uint32_t dest); +uint32_t apic_id(void); + +int enable_x2apic(void); + +#endif diff --git a/kvm-unittest/lib/x86/atomic.h b/kvm-unittest/lib/x86/atomic.h new file mode 100644 index 0000000..de2f033 --- /dev/null +++ b/kvm-unittest/lib/x86/atomic.h @@ -0,0 +1,164 @@ +#ifndef __ATOMIC_H +#define __ATOMIC_H + +typedef struct { + volatile int counter; +} atomic_t; + +#ifdef __i386__ + +/** + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + */ +static inline int atomic_read(const atomic_t *v) +{ + return v->counter; +} + +/** + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + */ +static inline void atomic_set(atomic_t *v, int i) +{ + v->counter = i; +} + +/** + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + */ +static inline void atomic_inc(atomic_t *v) +{ + asm volatile("lock incl %0" + : "+m" (v->counter)); +} + +/** + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + */ +static inline void atomic_dec(atomic_t *v) +{ + asm volatile("lock decl %0" + : "+m" (v->counter)); +} + +typedef struct { + u64 __attribute__((aligned(8))) counter; +} atomic64_t; + +#define ATOMIC64_INIT(val) { (val) } + +/** + * atomic64_read - read atomic64 variable + * @ptr: pointer to type atomic64_t + * + * Atomically reads the value of @ptr and returns it. + */ +static inline u64 atomic64_read(atomic64_t *ptr) +{ + u64 res; + + /* + * Note, we inline this atomic64_t primitive because + * it only clobbers EAX/EDX and leaves the others + * untouched. We also (somewhat subtly) rely on the + * fact that cmpxchg8b returns the current 64-bit value + * of the memory location we are touching: + */ + asm volatile("mov %%ebx, %%eax\n\t" + "mov %%ecx, %%edx\n\t" + "lock cmpxchg8b %1\n" + : "=&A" (res) + : "m" (*ptr) + ); + return res; +} + +u64 atomic64_cmpxchg(atomic64_t *v, u64 old, u64 new); + +#elif defined(__x86_64__) + +/** + * atomic_read - read atomic variable + * @v: pointer of type atomic_t + * + * Atomically reads the value of @v. + */ +static inline int atomic_read(const atomic_t *v) +{ + return v->counter; +} + +/** + * atomic_set - set atomic variable + * @v: pointer of type atomic_t + * @i: required value + * + * Atomically sets the value of @v to @i. + */ +static inline void atomic_set(atomic_t *v, int i) +{ + v->counter = i; +} + +/** + * atomic_inc - increment atomic variable + * @v: pointer of type atomic_t + * + * Atomically increments @v by 1. + */ +static inline void atomic_inc(atomic_t *v) +{ + asm volatile("lock incl %0" + : "=m" (v->counter) + : "m" (v->counter)); +} + +/** + * atomic_dec - decrement atomic variable + * @v: pointer of type atomic_t + * + * Atomically decrements @v by 1. + */ +static inline void atomic_dec(atomic_t *v) +{ + asm volatile("lock decl %0" + : "=m" (v->counter) + : "m" (v->counter)); +} + +typedef struct { + long long counter; +} atomic64_t; + +#define ATOMIC64_INIT(i) { (i) } + +/** + * atomic64_read - read atomic64 variable + * @v: pointer of type atomic64_t + * + * Atomically reads the value of @v. + * Doesn't imply a read memory barrier. + */ +static inline long atomic64_read(const atomic64_t *v) +{ + return v->counter; +} + +u64 atomic64_cmpxchg(atomic64_t *v, u64 old, u64 new); + +#endif + +#endif diff --git a/kvm-unittest/lib/x86/desc.h b/kvm-unittest/lib/x86/desc.h new file mode 100644 index 0000000..f819452 --- /dev/null +++ b/kvm-unittest/lib/x86/desc.h @@ -0,0 +1,87 @@ +#ifndef __IDT_TEST__ +#define __IDT_TEST__ + +void setup_idt(void); +#ifndef __x86_64__ +void setup_gdt(void); +void setup_tss32(void); +#else +static inline void setup_gdt(void){} +static inline void setup_tss32(void){} +#endif + +struct ex_regs { + unsigned long rax, rcx, rdx, rbx; + unsigned long dummy, rbp, rsi, rdi; +#ifdef __x86_64__ + unsigned long r8, r9, r10, r11; + unsigned long r12, r13, r14, r15; +#endif + unsigned long vector; + unsigned long error_code; + unsigned long rip; + unsigned long cs; + unsigned long rflags; +}; + +typedef struct { + u16 prev; + u16 res1; + u32 esp0; + u16 ss0; + u16 res2; + u32 esp1; + u16 ss1; + u16 res3; + u32 esp2; + u16 ss2; + u16 res4; + u32 cr3; + u32 eip; + u32 eflags; + u32 eax, ecx, edx, ebx, esp, ebp, esi, edi; + u16 es; + u16 res5; + u16 cs; + u16 res6; + u16 ss; + u16 res7; + u16 ds; + u16 res8; + u16 fs; + u16 res9; + u16 gs; + u16 res10; + u16 ldt; + u16 res11; + u16 t:1; + u16 res12:15; + u16 iomap_base; +} tss32_t; + +#define ASM_TRY(catch) \ + "movl $0, %%gs:4 \n\t" \ + ".pushsection .data.ex \n\t" \ + ".quad 1111f, " catch "\n\t" \ + ".popsection \n\t" \ + "1111:" + +#define UD_VECTOR 6 +#define GP_VECTOR 13 + +#define TSS_MAIN 0x20 +#define TSS_INTR 0x28 + +#define NP_SEL 0x18 + +unsigned exception_vector(void); +unsigned exception_error_code(void); +void set_idt_entry(int vec, void *addr, int dpl); +void set_idt_sel(int vec, u16 sel); +void set_gdt_entry(int num, u32 base, u32 limit, u8 access, u8 gran); +void set_idt_task_gate(int vec, u16 sel); +void set_intr_task_gate(int e, void *fn); +void print_current_tss_info(void); +void handle_exception(u8 v, void (*func)(struct ex_regs *regs)); + +#endif diff --git a/kvm-unittest/lib/x86/fake-apic.h b/kvm-unittest/lib/x86/fake-apic.h new file mode 100644 index 0000000..eed63ba --- /dev/null +++ b/kvm-unittest/lib/x86/fake-apic.h @@ -0,0 +1,14 @@ +#ifndef SILLY_APIC_H +#define SILLY_APIC_H + +#define APIC_BASE 0x1000 +#define APIC_SIZE 0x100 + +#define APIC_REG_NCPU 0x00 +#define APIC_REG_ID 0x04 +#define APIC_REG_SIPI_ADDR 0x08 +#define APIC_REG_SEND_SIPI 0x0c +#define APIC_REG_IPI_VECTOR 0x10 +#define APIC_REG_SEND_IPI 0x14 + +#endif diff --git a/kvm-unittest/lib/x86/fwcfg.h b/kvm-unittest/lib/x86/fwcfg.h new file mode 100644 index 0000000..e0836ca --- /dev/null +++ b/kvm-unittest/lib/x86/fwcfg.h @@ -0,0 +1,44 @@ +#ifndef FWCFG_H +#define FWCFG_H + +#include + +#define FW_CFG_SIGNATURE 0x00 +#define FW_CFG_ID 0x01 +#define FW_CFG_UUID 0x02 +#define FW_CFG_RAM_SIZE 0x03 +#define FW_CFG_NOGRAPHIC 0x04 +#define FW_CFG_NB_CPUS 0x05 +#define FW_CFG_MACHINE_ID 0x06 +#define FW_CFG_KERNEL_ADDR 0x07 +#define FW_CFG_KERNEL_SIZE 0x08 +#define FW_CFG_KERNEL_CMDLINE 0x09 +#define FW_CFG_INITRD_ADDR 0x0a +#define FW_CFG_INITRD_SIZE 0x0b +#define FW_CFG_BOOT_DEVICE 0x0c +#define FW_CFG_NUMA 0x0d +#define FW_CFG_BOOT_MENU 0x0e +#define FW_CFG_MAX_CPUS 0x0f +#define FW_CFG_MAX_ENTRY 0x10 + +#define FW_CFG_WRITE_CHANNEL 0x4000 +#define FW_CFG_ARCH_LOCAL 0x8000 +#define FW_CFG_ENTRY_MASK ~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL) + +#define FW_CFG_INVALID 0xffff + +#define BIOS_CFG_IOPORT 0x510 + +#define FW_CFG_ACPI_TABLES (FW_CFG_ARCH_LOCAL + 0) +#define FW_CFG_SMBIOS_ENTRIES (FW_CFG_ARCH_LOCAL + 1) +#define FW_CFG_IRQ0_OVERRIDE (FW_CFG_ARCH_LOCAL + 2) + +uint8_t fwcfg_get_u8(unsigned index); +uint16_t fwcfg_get_u16(unsigned index); +uint32_t fwcfg_get_u32(unsigned index); +uint64_t fwcfg_get_u64(unsigned index); + +unsigned fwcfg_get_nb_cpus(void); + +#endif + diff --git a/kvm-unittest/lib/x86/io.h b/kvm-unittest/lib/x86/io.h new file mode 100644 index 0000000..bd6341c --- /dev/null +++ b/kvm-unittest/lib/x86/io.h @@ -0,0 +1,40 @@ +#ifndef IO_H +#define IO_H + +static inline unsigned char inb(unsigned short port) +{ + unsigned char value; + asm volatile("inb %w1, %0" : "=a" (value) : "Nd" (port)); + return value; +} + +static inline unsigned short inw(unsigned short port) +{ + unsigned short value; + asm volatile("inw %w1, %0" : "=a" (value) : "Nd" (port)); + return value; +} + +static inline unsigned int inl(unsigned short port) +{ + unsigned int value; + asm volatile("inl %w1, %0" : "=a" (value) : "Nd" (port)); + return value; +} + +static inline void outb(unsigned char value, unsigned short port) +{ + asm volatile("outb %b0, %w1" : : "a"(value), "Nd"(port)); +} + +static inline void outw(unsigned short value, unsigned short port) +{ + asm volatile("outw %w0, %w1" : : "a"(value), "Nd"(port)); +} + +static inline void outl(unsigned int value, unsigned short port) +{ + asm volatile("outl %0, %w1" : : "a"(value), "Nd"(port)); +} + +#endif diff --git a/kvm-unittest/lib/x86/isr.h b/kvm-unittest/lib/x86/isr.h new file mode 100644 index 0000000..b07a32a --- /dev/null +++ b/kvm-unittest/lib/x86/isr.h @@ -0,0 +1,14 @@ +#ifndef __ISR_TEST__ +#define __ISR_TEST__ + +typedef struct { + ulong regs[sizeof(ulong)*2]; + ulong func; + ulong rip; + ulong cs; + ulong rflags; +} isr_regs_t; + +void handle_irq(unsigned vec, void (*func)(isr_regs_t *regs)); + +#endif diff --git a/kvm-unittest/lib/x86/msr.h b/kvm-unittest/lib/x86/msr.h new file mode 100644 index 0000000..281255a --- /dev/null +++ b/kvm-unittest/lib/x86/msr.h @@ -0,0 +1,411 @@ +#ifndef _ASM_X86_MSR_INDEX_H +#define _ASM_X86_MSR_INDEX_H + +/* CPU model specific register (MSR) numbers */ + +/* x86-64 specific MSRs */ +#define MSR_EFER 0xc0000080 /* extended feature register */ +#define MSR_STAR 0xc0000081 /* legacy mode SYSCALL target */ +#define MSR_LSTAR 0xc0000082 /* long mode SYSCALL target */ +#define MSR_CSTAR 0xc0000083 /* compat mode SYSCALL target */ +#define MSR_SYSCALL_MASK 0xc0000084 /* EFLAGS mask for syscall */ +#define MSR_FS_BASE 0xc0000100 /* 64bit FS base */ +#define MSR_GS_BASE 0xc0000101 /* 64bit GS base */ +#define MSR_KERNEL_GS_BASE 0xc0000102 /* SwapGS GS shadow */ +#define MSR_TSC_AUX 0xc0000103 /* Auxiliary TSC */ + +/* EFER bits: */ +#define _EFER_SCE 0 /* SYSCALL/SYSRET */ +#define _EFER_LME 8 /* Long mode enable */ +#define _EFER_LMA 10 /* Long mode active (read-only) */ +#define _EFER_NX 11 /* No execute enable */ +#define _EFER_SVME 12 /* Enable virtualization */ +#define _EFER_LMSLE 13 /* Long Mode Segment Limit Enable */ +#define _EFER_FFXSR 14 /* Enable Fast FXSAVE/FXRSTOR */ + +#define EFER_SCE (1<<_EFER_SCE) +#define EFER_LME (1<<_EFER_LME) +#define EFER_LMA (1<<_EFER_LMA) +#define EFER_NX (1<<_EFER_NX) +#define EFER_SVME (1<<_EFER_SVME) +#define EFER_LMSLE (1<<_EFER_LMSLE) +#define EFER_FFXSR (1<<_EFER_FFXSR) + +/* Intel MSRs. Some also available on other CPUs */ +#define MSR_IA32_PERFCTR0 0x000000c1 +#define MSR_IA32_PERFCTR1 0x000000c2 +#define MSR_FSB_FREQ 0x000000cd + +#define MSR_MTRRcap 0x000000fe +#define MSR_IA32_BBL_CR_CTL 0x00000119 + +#define MSR_IA32_SYSENTER_CS 0x00000174 +#define MSR_IA32_SYSENTER_ESP 0x00000175 +#define MSR_IA32_SYSENTER_EIP 0x00000176 + +#define MSR_IA32_MCG_CAP 0x00000179 +#define MSR_IA32_MCG_STATUS 0x0000017a +#define MSR_IA32_MCG_CTL 0x0000017b + +#define MSR_IA32_PEBS_ENABLE 0x000003f1 +#define MSR_IA32_DS_AREA 0x00000600 +#define MSR_IA32_PERF_CAPABILITIES 0x00000345 + +#define MSR_MTRRfix64K_00000 0x00000250 +#define MSR_MTRRfix16K_80000 0x00000258 +#define MSR_MTRRfix16K_A0000 0x00000259 +#define MSR_MTRRfix4K_C0000 0x00000268 +#define MSR_MTRRfix4K_C8000 0x00000269 +#define MSR_MTRRfix4K_D0000 0x0000026a +#define MSR_MTRRfix4K_D8000 0x0000026b +#define MSR_MTRRfix4K_E0000 0x0000026c +#define MSR_MTRRfix4K_E8000 0x0000026d +#define MSR_MTRRfix4K_F0000 0x0000026e +#define MSR_MTRRfix4K_F8000 0x0000026f +#define MSR_MTRRdefType 0x000002ff + +#define MSR_IA32_CR_PAT 0x00000277 + +#define MSR_IA32_DEBUGCTLMSR 0x000001d9 +#define MSR_IA32_LASTBRANCHFROMIP 0x000001db +#define MSR_IA32_LASTBRANCHTOIP 0x000001dc +#define MSR_IA32_LASTINTFROMIP 0x000001dd +#define MSR_IA32_LASTINTTOIP 0x000001de + +/* DEBUGCTLMSR bits (others vary by model): */ +#define DEBUGCTLMSR_LBR (1UL << 0) /* last branch recording */ +#define DEBUGCTLMSR_BTF (1UL << 1) /* single-step on branches */ +#define DEBUGCTLMSR_TR (1UL << 6) +#define DEBUGCTLMSR_BTS (1UL << 7) +#define DEBUGCTLMSR_BTINT (1UL << 8) +#define DEBUGCTLMSR_BTS_OFF_OS (1UL << 9) +#define DEBUGCTLMSR_BTS_OFF_USR (1UL << 10) +#define DEBUGCTLMSR_FREEZE_LBRS_ON_PMI (1UL << 11) + +#define MSR_IA32_MC0_CTL 0x00000400 +#define MSR_IA32_MC0_STATUS 0x00000401 +#define MSR_IA32_MC0_ADDR 0x00000402 +#define MSR_IA32_MC0_MISC 0x00000403 + +#define MSR_IA32_MCx_CTL(x) (MSR_IA32_MC0_CTL + 4*(x)) +#define MSR_IA32_MCx_STATUS(x) (MSR_IA32_MC0_STATUS + 4*(x)) +#define MSR_IA32_MCx_ADDR(x) (MSR_IA32_MC0_ADDR + 4*(x)) +#define MSR_IA32_MCx_MISC(x) (MSR_IA32_MC0_MISC + 4*(x)) + +/* These are consecutive and not in the normal 4er MCE bank block */ +#define MSR_IA32_MC0_CTL2 0x00000280 +#define MSR_IA32_MCx_CTL2(x) (MSR_IA32_MC0_CTL2 + (x)) + +#define CMCI_EN (1ULL << 30) +#define CMCI_THRESHOLD_MASK 0xffffULL + +#define MSR_P6_PERFCTR0 0x000000c1 +#define MSR_P6_PERFCTR1 0x000000c2 +#define MSR_P6_EVNTSEL0 0x00000186 +#define MSR_P6_EVNTSEL1 0x00000187 + +/* AMD64 MSRs. Not complete. See the architecture manual for a more + complete list. */ + +#define MSR_AMD64_PATCH_LEVEL 0x0000008b +#define MSR_AMD64_NB_CFG 0xc001001f +#define MSR_AMD64_PATCH_LOADER 0xc0010020 +#define MSR_AMD64_OSVW_ID_LENGTH 0xc0010140 +#define MSR_AMD64_OSVW_STATUS 0xc0010141 +#define MSR_AMD64_DC_CFG 0xc0011022 +#define MSR_AMD64_IBSFETCHCTL 0xc0011030 +#define MSR_AMD64_IBSFETCHLINAD 0xc0011031 +#define MSR_AMD64_IBSFETCHPHYSAD 0xc0011032 +#define MSR_AMD64_IBSOPCTL 0xc0011033 +#define MSR_AMD64_IBSOPRIP 0xc0011034 +#define MSR_AMD64_IBSOPDATA 0xc0011035 +#define MSR_AMD64_IBSOPDATA2 0xc0011036 +#define MSR_AMD64_IBSOPDATA3 0xc0011037 +#define MSR_AMD64_IBSDCLINAD 0xc0011038 +#define MSR_AMD64_IBSDCPHYSAD 0xc0011039 +#define MSR_AMD64_IBSCTL 0xc001103a + +/* Fam 10h MSRs */ +#define MSR_FAM10H_MMIO_CONF_BASE 0xc0010058 +#define FAM10H_MMIO_CONF_ENABLE (1<<0) +#define FAM10H_MMIO_CONF_BUSRANGE_MASK 0xf +#define FAM10H_MMIO_CONF_BUSRANGE_SHIFT 2 +#define FAM10H_MMIO_CONF_BASE_MASK 0xfffffff +#define FAM10H_MMIO_CONF_BASE_SHIFT 20 +#define MSR_FAM10H_NODE_ID 0xc001100c + +/* K8 MSRs */ +#define MSR_K8_TOP_MEM1 0xc001001a +#define MSR_K8_TOP_MEM2 0xc001001d +#define MSR_K8_SYSCFG 0xc0010010 +#define MSR_K8_INT_PENDING_MSG 0xc0010055 +/* C1E active bits in int pending message */ +#define K8_INTP_C1E_ACTIVE_MASK 0x18000000 +#define MSR_K8_TSEG_ADDR 0xc0010112 +#define K8_MTRRFIXRANGE_DRAM_ENABLE 0x00040000 /* MtrrFixDramEn bit */ +#define K8_MTRRFIXRANGE_DRAM_MODIFY 0x00080000 /* MtrrFixDramModEn bit */ +#define K8_MTRR_RDMEM_WRMEM_MASK 0x18181818 /* Mask: RdMem|WrMem */ + +/* K7 MSRs */ +#define MSR_K7_EVNTSEL0 0xc0010000 +#define MSR_K7_PERFCTR0 0xc0010004 +#define MSR_K7_EVNTSEL1 0xc0010001 +#define MSR_K7_PERFCTR1 0xc0010005 +#define MSR_K7_EVNTSEL2 0xc0010002 +#define MSR_K7_PERFCTR2 0xc0010006 +#define MSR_K7_EVNTSEL3 0xc0010003 +#define MSR_K7_PERFCTR3 0xc0010007 +#define MSR_K7_CLK_CTL 0xc001001b +#define MSR_K7_HWCR 0xc0010015 +#define MSR_K7_FID_VID_CTL 0xc0010041 +#define MSR_K7_FID_VID_STATUS 0xc0010042 + +/* K6 MSRs */ +#define MSR_K6_EFER 0xc0000080 +#define MSR_K6_STAR 0xc0000081 +#define MSR_K6_WHCR 0xc0000082 +#define MSR_K6_UWCCR 0xc0000085 +#define MSR_K6_EPMR 0xc0000086 +#define MSR_K6_PSOR 0xc0000087 +#define MSR_K6_PFIR 0xc0000088 + +/* Centaur-Hauls/IDT defined MSRs. */ +#define MSR_IDT_FCR1 0x00000107 +#define MSR_IDT_FCR2 0x00000108 +#define MSR_IDT_FCR3 0x00000109 +#define MSR_IDT_FCR4 0x0000010a + +#define MSR_IDT_MCR0 0x00000110 +#define MSR_IDT_MCR1 0x00000111 +#define MSR_IDT_MCR2 0x00000112 +#define MSR_IDT_MCR3 0x00000113 +#define MSR_IDT_MCR4 0x00000114 +#define MSR_IDT_MCR5 0x00000115 +#define MSR_IDT_MCR6 0x00000116 +#define MSR_IDT_MCR7 0x00000117 +#define MSR_IDT_MCR_CTRL 0x00000120 + +/* VIA Cyrix defined MSRs*/ +#define MSR_VIA_FCR 0x00001107 +#define MSR_VIA_LONGHAUL 0x0000110a +#define MSR_VIA_RNG 0x0000110b +#define MSR_VIA_BCR2 0x00001147 + +/* Transmeta defined MSRs */ +#define MSR_TMTA_LONGRUN_CTRL 0x80868010 +#define MSR_TMTA_LONGRUN_FLAGS 0x80868011 +#define MSR_TMTA_LRTI_READOUT 0x80868018 +#define MSR_TMTA_LRTI_VOLT_MHZ 0x8086801a + +/* Intel defined MSRs. */ +#define MSR_IA32_P5_MC_ADDR 0x00000000 +#define MSR_IA32_P5_MC_TYPE 0x00000001 +#define MSR_IA32_TSC 0x00000010 +#define MSR_IA32_PLATFORM_ID 0x00000017 +#define MSR_IA32_EBL_CR_POWERON 0x0000002a +#define MSR_IA32_FEATURE_CONTROL 0x0000003a + +#define FEATURE_CONTROL_LOCKED (1<<0) +#define FEATURE_CONTROL_VMXON_ENABLED_INSIDE_SMX (1<<1) +#define FEATURE_CONTROL_VMXON_ENABLED_OUTSIDE_SMX (1<<2) + +#define MSR_IA32_APICBASE 0x0000001b +#define MSR_IA32_APICBASE_BSP (1<<8) +#define MSR_IA32_APICBASE_ENABLE (1<<11) +#define MSR_IA32_APICBASE_BASE (0xfffff<<12) + +#define MSR_IA32_UCODE_WRITE 0x00000079 +#define MSR_IA32_UCODE_REV 0x0000008b + +#define MSR_IA32_PERF_STATUS 0x00000198 +#define MSR_IA32_PERF_CTL 0x00000199 + +#define MSR_IA32_MPERF 0x000000e7 +#define MSR_IA32_APERF 0x000000e8 + +#define MSR_IA32_THERM_CONTROL 0x0000019a +#define MSR_IA32_THERM_INTERRUPT 0x0000019b + +#define THERM_INT_LOW_ENABLE (1 << 0) +#define THERM_INT_HIGH_ENABLE (1 << 1) + +#define MSR_IA32_THERM_STATUS 0x0000019c + +#define THERM_STATUS_PROCHOT (1 << 0) + +#define MSR_THERM2_CTL 0x0000019d + +#define MSR_THERM2_CTL_TM_SELECT (1ULL << 16) + +#define MSR_IA32_MISC_ENABLE 0x000001a0 + +#define MSR_IA32_TEMPERATURE_TARGET 0x000001a2 + +/* MISC_ENABLE bits: architectural */ +#define MSR_IA32_MISC_ENABLE_FAST_STRING (1ULL << 0) +#define MSR_IA32_MISC_ENABLE_TCC (1ULL << 1) +#define MSR_IA32_MISC_ENABLE_EMON (1ULL << 7) +#define MSR_IA32_MISC_ENABLE_BTS_UNAVAIL (1ULL << 11) +#define MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL (1ULL << 12) +#define MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP (1ULL << 16) +#define MSR_IA32_MISC_ENABLE_MWAIT (1ULL << 18) +#define MSR_IA32_MISC_ENABLE_LIMIT_CPUID (1ULL << 22) +#define MSR_IA32_MISC_ENABLE_XTPR_DISABLE (1ULL << 23) +#define MSR_IA32_MISC_ENABLE_XD_DISABLE (1ULL << 34) + +/* MISC_ENABLE bits: model-specific, meaning may vary from core to core */ +#define MSR_IA32_MISC_ENABLE_X87_COMPAT (1ULL << 2) +#define MSR_IA32_MISC_ENABLE_TM1 (1ULL << 3) +#define MSR_IA32_MISC_ENABLE_SPLIT_LOCK_DISABLE (1ULL << 4) +#define MSR_IA32_MISC_ENABLE_L3CACHE_DISABLE (1ULL << 6) +#define MSR_IA32_MISC_ENABLE_SUPPRESS_LOCK (1ULL << 8) +#define MSR_IA32_MISC_ENABLE_PREFETCH_DISABLE (1ULL << 9) +#define MSR_IA32_MISC_ENABLE_FERR (1ULL << 10) +#define MSR_IA32_MISC_ENABLE_FERR_MULTIPLEX (1ULL << 10) +#define MSR_IA32_MISC_ENABLE_TM2 (1ULL << 13) +#define MSR_IA32_MISC_ENABLE_ADJ_PREF_DISABLE (1ULL << 19) +#define MSR_IA32_MISC_ENABLE_SPEEDSTEP_LOCK (1ULL << 20) +#define MSR_IA32_MISC_ENABLE_L1D_CONTEXT (1ULL << 24) +#define MSR_IA32_MISC_ENABLE_DCU_PREF_DISABLE (1ULL << 37) +#define MSR_IA32_MISC_ENABLE_TURBO_DISABLE (1ULL << 38) +#define MSR_IA32_MISC_ENABLE_IP_PREF_DISABLE (1ULL << 39) + +/* P4/Xeon+ specific */ +#define MSR_IA32_MCG_EAX 0x00000180 +#define MSR_IA32_MCG_EBX 0x00000181 +#define MSR_IA32_MCG_ECX 0x00000182 +#define MSR_IA32_MCG_EDX 0x00000183 +#define MSR_IA32_MCG_ESI 0x00000184 +#define MSR_IA32_MCG_EDI 0x00000185 +#define MSR_IA32_MCG_EBP 0x00000186 +#define MSR_IA32_MCG_ESP 0x00000187 +#define MSR_IA32_MCG_EFLAGS 0x00000188 +#define MSR_IA32_MCG_EIP 0x00000189 +#define MSR_IA32_MCG_RESERVED 0x0000018a + +/* Pentium IV performance counter MSRs */ +#define MSR_P4_BPU_PERFCTR0 0x00000300 +#define MSR_P4_BPU_PERFCTR1 0x00000301 +#define MSR_P4_BPU_PERFCTR2 0x00000302 +#define MSR_P4_BPU_PERFCTR3 0x00000303 +#define MSR_P4_MS_PERFCTR0 0x00000304 +#define MSR_P4_MS_PERFCTR1 0x00000305 +#define MSR_P4_MS_PERFCTR2 0x00000306 +#define MSR_P4_MS_PERFCTR3 0x00000307 +#define MSR_P4_FLAME_PERFCTR0 0x00000308 +#define MSR_P4_FLAME_PERFCTR1 0x00000309 +#define MSR_P4_FLAME_PERFCTR2 0x0000030a +#define MSR_P4_FLAME_PERFCTR3 0x0000030b +#define MSR_P4_IQ_PERFCTR0 0x0000030c +#define MSR_P4_IQ_PERFCTR1 0x0000030d +#define MSR_P4_IQ_PERFCTR2 0x0000030e +#define MSR_P4_IQ_PERFCTR3 0x0000030f +#define MSR_P4_IQ_PERFCTR4 0x00000310 +#define MSR_P4_IQ_PERFCTR5 0x00000311 +#define MSR_P4_BPU_CCCR0 0x00000360 +#define MSR_P4_BPU_CCCR1 0x00000361 +#define MSR_P4_BPU_CCCR2 0x00000362 +#define MSR_P4_BPU_CCCR3 0x00000363 +#define MSR_P4_MS_CCCR0 0x00000364 +#define MSR_P4_MS_CCCR1 0x00000365 +#define MSR_P4_MS_CCCR2 0x00000366 +#define MSR_P4_MS_CCCR3 0x00000367 +#define MSR_P4_FLAME_CCCR0 0x00000368 +#define MSR_P4_FLAME_CCCR1 0x00000369 +#define MSR_P4_FLAME_CCCR2 0x0000036a +#define MSR_P4_FLAME_CCCR3 0x0000036b +#define MSR_P4_IQ_CCCR0 0x0000036c +#define MSR_P4_IQ_CCCR1 0x0000036d +#define MSR_P4_IQ_CCCR2 0x0000036e +#define MSR_P4_IQ_CCCR3 0x0000036f +#define MSR_P4_IQ_CCCR4 0x00000370 +#define MSR_P4_IQ_CCCR5 0x00000371 +#define MSR_P4_ALF_ESCR0 0x000003ca +#define MSR_P4_ALF_ESCR1 0x000003cb +#define MSR_P4_BPU_ESCR0 0x000003b2 +#define MSR_P4_BPU_ESCR1 0x000003b3 +#define MSR_P4_BSU_ESCR0 0x000003a0 +#define MSR_P4_BSU_ESCR1 0x000003a1 +#define MSR_P4_CRU_ESCR0 0x000003b8 +#define MSR_P4_CRU_ESCR1 0x000003b9 +#define MSR_P4_CRU_ESCR2 0x000003cc +#define MSR_P4_CRU_ESCR3 0x000003cd +#define MSR_P4_CRU_ESCR4 0x000003e0 +#define MSR_P4_CRU_ESCR5 0x000003e1 +#define MSR_P4_DAC_ESCR0 0x000003a8 +#define MSR_P4_DAC_ESCR1 0x000003a9 +#define MSR_P4_FIRM_ESCR0 0x000003a4 +#define MSR_P4_FIRM_ESCR1 0x000003a5 +#define MSR_P4_FLAME_ESCR0 0x000003a6 +#define MSR_P4_FLAME_ESCR1 0x000003a7 +#define MSR_P4_FSB_ESCR0 0x000003a2 +#define MSR_P4_FSB_ESCR1 0x000003a3 +#define MSR_P4_IQ_ESCR0 0x000003ba +#define MSR_P4_IQ_ESCR1 0x000003bb +#define MSR_P4_IS_ESCR0 0x000003b4 +#define MSR_P4_IS_ESCR1 0x000003b5 +#define MSR_P4_ITLB_ESCR0 0x000003b6 +#define MSR_P4_ITLB_ESCR1 0x000003b7 +#define MSR_P4_IX_ESCR0 0x000003c8 +#define MSR_P4_IX_ESCR1 0x000003c9 +#define MSR_P4_MOB_ESCR0 0x000003aa +#define MSR_P4_MOB_ESCR1 0x000003ab +#define MSR_P4_MS_ESCR0 0x000003c0 +#define MSR_P4_MS_ESCR1 0x000003c1 +#define MSR_P4_PMH_ESCR0 0x000003ac +#define MSR_P4_PMH_ESCR1 0x000003ad +#define MSR_P4_RAT_ESCR0 0x000003bc +#define MSR_P4_RAT_ESCR1 0x000003bd +#define MSR_P4_SAAT_ESCR0 0x000003ae +#define MSR_P4_SAAT_ESCR1 0x000003af +#define MSR_P4_SSU_ESCR0 0x000003be +#define MSR_P4_SSU_ESCR1 0x000003bf /* guess: not in manual */ + +#define MSR_P4_TBPU_ESCR0 0x000003c2 +#define MSR_P4_TBPU_ESCR1 0x000003c3 +#define MSR_P4_TC_ESCR0 0x000003c4 +#define MSR_P4_TC_ESCR1 0x000003c5 +#define MSR_P4_U2L_ESCR0 0x000003b0 +#define MSR_P4_U2L_ESCR1 0x000003b1 + +#define MSR_P4_PEBS_MATRIX_VERT 0x000003f2 + +/* Intel Core-based CPU performance counters */ +#define MSR_CORE_PERF_FIXED_CTR0 0x00000309 +#define MSR_CORE_PERF_FIXED_CTR1 0x0000030a +#define MSR_CORE_PERF_FIXED_CTR2 0x0000030b +#define MSR_CORE_PERF_FIXED_CTR_CTRL 0x0000038d +#define MSR_CORE_PERF_GLOBAL_STATUS 0x0000038e +#define MSR_CORE_PERF_GLOBAL_CTRL 0x0000038f +#define MSR_CORE_PERF_GLOBAL_OVF_CTRL 0x00000390 + +/* Geode defined MSRs */ +#define MSR_GEODE_BUSCONT_CONF0 0x00001900 + +/* Intel VT MSRs */ +#define MSR_IA32_VMX_BASIC 0x00000480 +#define MSR_IA32_VMX_PINBASED_CTLS 0x00000481 +#define MSR_IA32_VMX_PROCBASED_CTLS 0x00000482 +#define MSR_IA32_VMX_EXIT_CTLS 0x00000483 +#define MSR_IA32_VMX_ENTRY_CTLS 0x00000484 +#define MSR_IA32_VMX_MISC 0x00000485 +#define MSR_IA32_VMX_CR0_FIXED0 0x00000486 +#define MSR_IA32_VMX_CR0_FIXED1 0x00000487 +#define MSR_IA32_VMX_CR4_FIXED0 0x00000488 +#define MSR_IA32_VMX_CR4_FIXED1 0x00000489 +#define MSR_IA32_VMX_VMCS_ENUM 0x0000048a +#define MSR_IA32_VMX_PROCBASED_CTLS2 0x0000048b +#define MSR_IA32_VMX_EPT_VPID_CAP 0x0000048c +#define MSR_IA32_VMX_TRUE_PIN 0x0000048d +#define MSR_IA32_VMX_TRUE_PROC 0x0000048e +#define MSR_IA32_VMX_TRUE_EXIT 0x0000048f +#define MSR_IA32_VMX_TRUE_ENTRY 0x00000490 + + +/* AMD-V MSRs */ + +#define MSR_VM_CR 0xc0010114 +#define MSR_VM_IGNNE 0xc0010115 +#define MSR_VM_HSAVE_PA 0xc0010117 + +#endif /* _ASM_X86_MSR_INDEX_H */ diff --git a/kvm-unittest/lib/x86/pci.h b/kvm-unittest/lib/x86/pci.h new file mode 100644 index 0000000..0f69ef0 --- /dev/null +++ b/kvm-unittest/lib/x86/pci.h @@ -0,0 +1,16 @@ +#ifndef PCI_H +#define PCI_H + +#include +#include "libcflat.h" + +typedef uint16_t pcidevaddr_t; +enum { + PCIDEVADDR_INVALID = 0x0 +}; +pcidevaddr_t pci_find_dev(uint16_t vendor_id, uint16_t device_id); +unsigned long pci_bar_addr(pcidevaddr_t dev, int bar_num); +bool pci_bar_is_memory(pcidevaddr_t dev, int bar_num); +bool pci_bar_is_valid(pcidevaddr_t dev, int bar_num); + +#endif diff --git a/kvm-unittest/lib/x86/processor.h b/kvm-unittest/lib/x86/processor.h new file mode 100644 index 0000000..29811d4 --- /dev/null +++ b/kvm-unittest/lib/x86/processor.h @@ -0,0 +1,325 @@ +#ifndef LIBCFLAT_PROCESSOR_H +#define LIBCFLAT_PROCESSOR_H + +#include "libcflat.h" +#include + +struct descriptor_table_ptr { + u16 limit; + ulong base; +} __attribute__((packed)); + +static inline void barrier(void) +{ + asm volatile ("" : : : "memory"); +} + +static inline u16 read_cs(void) +{ + unsigned val; + + asm ("mov %%cs, %0" : "=mr"(val)); + return val; +} + +static inline u16 read_ds(void) +{ + unsigned val; + + asm ("mov %%ds, %0" : "=mr"(val)); + return val; +} + +static inline u16 read_es(void) +{ + unsigned val; + + asm ("mov %%es, %0" : "=mr"(val)); + return val; +} + +static inline u16 read_ss(void) +{ + unsigned val; + + asm ("mov %%ss, %0" : "=mr"(val)); + return val; +} + +static inline u16 read_fs(void) +{ + unsigned val; + + asm ("mov %%fs, %0" : "=mr"(val)); + return val; +} + +static inline u16 read_gs(void) +{ + unsigned val; + + asm ("mov %%gs, %0" : "=mr"(val)); + return val; +} + +static inline unsigned long read_rflags(void) +{ + unsigned long f; + asm ("pushf; pop %0\n\t" : "=rm"(f)); + return f; +} + +static inline void write_ds(unsigned val) +{ + asm ("mov %0, %%ds" : : "rm"(val) : "memory"); +} + +static inline void write_es(unsigned val) +{ + asm ("mov %0, %%es" : : "rm"(val) : "memory"); +} + +static inline void write_ss(unsigned val) +{ + asm ("mov %0, %%ss" : : "rm"(val) : "memory"); +} + +static inline void write_fs(unsigned val) +{ + asm ("mov %0, %%fs" : : "rm"(val) : "memory"); +} + +static inline void write_gs(unsigned val) +{ + asm ("mov %0, %%gs" : : "rm"(val) : "memory"); +} + +static inline u64 rdmsr(u32 index) +{ + u32 a, d; + asm volatile ("rdmsr" : "=a"(a), "=d"(d) : "c"(index) : "memory"); + return a | ((u64)d << 32); +} + +static inline void wrmsr(u32 index, u64 val) +{ + u32 a = val, d = val >> 32; + asm volatile ("wrmsr" : : "a"(a), "d"(d), "c"(index) : "memory"); +} + +static inline uint64_t rdpmc(uint32_t index) +{ + uint32_t a, d; + asm volatile ("rdpmc" : "=a"(a), "=d"(d) : "c"(index)); + return a | ((uint64_t)d << 32); +} + +static inline void write_cr0(ulong val) +{ + asm volatile ("mov %0, %%cr0" : : "r"(val) : "memory"); +} + +static inline ulong read_cr0(void) +{ + ulong val; + asm volatile ("mov %%cr0, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline void write_cr2(ulong val) +{ + asm volatile ("mov %0, %%cr2" : : "r"(val) : "memory"); +} + +static inline ulong read_cr2(void) +{ + ulong val; + asm volatile ("mov %%cr2, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline void write_cr3(ulong val) +{ + asm volatile ("mov %0, %%cr3" : : "r"(val) : "memory"); +} + +static inline ulong read_cr3(void) +{ + ulong val; + asm volatile ("mov %%cr3, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline void write_cr4(ulong val) +{ + asm volatile ("mov %0, %%cr4" : : "r"(val) : "memory"); +} + +static inline ulong read_cr4(void) +{ + ulong val; + asm volatile ("mov %%cr4, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline void write_cr8(ulong val) +{ + asm volatile ("mov %0, %%cr8" : : "r"(val) : "memory"); +} + +static inline ulong read_cr8(void) +{ + ulong val; + asm volatile ("mov %%cr8, %0" : "=r"(val) : : "memory"); + return val; +} + +static inline void lgdt(const struct descriptor_table_ptr *ptr) +{ + asm volatile ("lgdt %0" : : "m"(*ptr)); +} + +static inline void sgdt(struct descriptor_table_ptr *ptr) +{ + asm volatile ("sgdt %0" : "=m"(*ptr)); +} + +static inline void lidt(const struct descriptor_table_ptr *ptr) +{ + asm volatile ("lidt %0" : : "m"(*ptr)); +} + +static inline void sidt(struct descriptor_table_ptr *ptr) +{ + asm volatile ("sidt %0" : "=m"(*ptr)); +} + +static inline void lldt(unsigned val) +{ + asm volatile ("lldt %0" : : "rm"(val)); +} + +static inline u16 sldt(void) +{ + u16 val; + asm volatile ("sldt %0" : "=rm"(val)); + return val; +} + +static inline void ltr(u16 val) +{ + asm volatile ("ltr %0" : : "rm"(val)); +} + +static inline u16 str(void) +{ + u16 val; + asm volatile ("str %0" : "=rm"(val)); + return val; +} + +static inline void write_dr6(ulong val) +{ + asm volatile ("mov %0, %%dr6" : : "r"(val) : "memory"); +} + +static inline ulong read_dr6(void) +{ + ulong val; + asm volatile ("mov %%dr6, %0" : "=r"(val)); + return val; +} + +static inline void write_dr7(ulong val) +{ + asm volatile ("mov %0, %%dr7" : : "r"(val) : "memory"); +} + +static inline ulong read_dr7(void) +{ + ulong val; + asm volatile ("mov %%dr7, %0" : "=r"(val)); + return val; +} + +struct cpuid { u32 a, b, c, d; }; + +static inline struct cpuid cpuid_indexed(u32 function, u32 index) +{ + struct cpuid r; + asm volatile ("cpuid" + : "=a"(r.a), "=b"(r.b), "=c"(r.c), "=d"(r.d) + : "0"(function), "2"(index)); + return r; +} + +static inline struct cpuid cpuid(u32 function) +{ + return cpuid_indexed(function, 0); +} + +static inline void pause(void) +{ + asm volatile ("pause"); +} + +static inline void cli(void) +{ + asm volatile ("cli"); +} + +static inline void sti(void) +{ + asm volatile ("sti"); +} + +static inline unsigned long long rdtsc() +{ + long long r; + +#ifdef __x86_64__ + unsigned a, d; + + asm volatile ("rdtsc" : "=a"(a), "=d"(d)); + r = a | ((long long)d << 32); +#else + asm volatile ("rdtsc" : "=A"(r)); +#endif + return r; +} + +static inline void wrtsc(u64 tsc) +{ + unsigned a = tsc, d = tsc >> 32; + + asm volatile("wrmsr" : : "a"(a), "d"(d), "c"(0x10)); +} + +static inline void irq_disable(void) +{ + asm volatile("cli"); +} + +static inline void irq_enable(void) +{ + asm volatile("sti"); +} + +static inline void invlpg(void *va) +{ + asm volatile("invlpg (%0)" ::"r" (va) : "memory"); +} + +static inline void safe_halt(void) +{ + asm volatile("sti; hlt"); +} + +#ifdef __x86_64__ +static inline void write_rflags(u64 r) +{ + asm volatile("push %0; popf\n\t" : : "q"(r) : "cc"); +} +#endif + +#endif diff --git a/kvm-unittest/lib/x86/smp.h b/kvm-unittest/lib/x86/smp.h new file mode 100644 index 0000000..df5fdba --- /dev/null +++ b/kvm-unittest/lib/x86/smp.h @@ -0,0 +1,21 @@ +#ifndef __SMP_H +#define __SMP_H + +#define mb() asm volatile("mfence":::"memory") +#define rmb() asm volatile("lfence":::"memory") +#define wmb() asm volatile("sfence" ::: "memory") + +struct spinlock { + int v; +}; + +void smp_init(void); + +int cpu_count(void); +int smp_id(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 spin_lock(struct spinlock *lock); +void spin_unlock(struct spinlock *lock); + +#endif diff --git a/kvm-unittest/lib/x86/vm.h b/kvm-unittest/lib/x86/vm.h new file mode 100644 index 0000000..eff6f72 --- /dev/null +++ b/kvm-unittest/lib/x86/vm.h @@ -0,0 +1,71 @@ +#ifndef VM_H +#define VM_H + +#include "processor.h" + +#define PAGE_SIZE 4096ul +#ifdef __x86_64__ +#define LARGE_PAGE_SIZE (512 * PAGE_SIZE) +#else +#define LARGE_PAGE_SIZE (1024 * PAGE_SIZE) +#endif + +#define PTE_PRESENT (1ull << 0) +#define PTE_PSE (1ull << 7) +#define PTE_WRITE (1ull << 1) +#define PTE_USER (1ull << 2) +#define PTE_ADDR (0xffffffffff000ull) + +#define X86_CR0_PE 0x00000001 +#define X86_CR0_WP 0x00010000 +#define X86_CR0_PG 0x80000000 +#define X86_CR4_VMXE 0x00000001 +#define X86_CR4_PSE 0x00000010 +#define X86_CR4_PAE 0x00000020 +#define X86_CR4_PCIDE 0x00020000 + +#ifdef __x86_64__ +#define SEL_NULL_DESC 0x0 +#define SEL_KERN_CODE_64 0x8 +#define SEL_KERN_DATA_64 0x10 +#define SEL_USER_CODE_64 0x18 +#define SEL_USER_DATA_64 0x20 +#define SEL_CODE_32 0x28 +#define SEL_DATA_32 0x30 +#define SEL_CODE_16 0x38 +#define SEL_DATA_16 0x40 +#define SEL_TSS_RUN 0x48 +#endif + +void setup_vm(); + +void *vmalloc(unsigned long size); +void vfree(void *mem); +void *vmap(unsigned long long phys, unsigned long size); +void *alloc_vpage(void); +void *alloc_vpages(ulong nr); +uint64_t virt_to_phys_cr3(void *mem); + +void install_pte(unsigned long *cr3, + int pte_level, + void *virt, + unsigned long pte, + unsigned long *pt_page); + +void *alloc_page(); + +void install_large_page(unsigned long *cr3,unsigned long phys, + void *virt); +void install_page(unsigned long *cr3, unsigned long phys, void *virt); + +static inline unsigned long virt_to_phys(const void *virt) +{ + return (unsigned long)virt; +} + +static inline void *phys_to_virt(unsigned long phys) +{ + return (void *)phys; +} + +#endif diff --git a/kvm-unittest/x86/ioram.h b/kvm-unittest/x86/ioram.h new file mode 100644 index 0000000..2938142 --- /dev/null +++ b/kvm-unittest/x86/ioram.h @@ -0,0 +1,7 @@ +#ifndef __IO_RAM_H +#define __IO_RAM_H + +#define IORAM_BASE_PHYS 0xff000000UL +#define IORAM_LEN 0x10000UL + +#endif diff --git a/kvm-unittest/x86/kvmclock.h b/kvm-unittest/x86/kvmclock.h new file mode 100644 index 0000000..166a338 --- /dev/null +++ b/kvm-unittest/x86/kvmclock.h @@ -0,0 +1,60 @@ +#ifndef KVMCLOCK_H +#define KVMCLOCK_H + +#define MSR_KVM_WALL_CLOCK 0x11 +#define MSR_KVM_SYSTEM_TIME 0x12 + +#define MAX_CPU 64 + +#define PVCLOCK_TSC_STABLE_BIT (1 << 0) +#define PVCLOCK_RAW_CYCLE_BIT (1 << 7) /* Get raw cycle */ + +# define NSEC_PER_SEC 1000000000ULL + +typedef u64 cycle_t; + +struct pvclock_vcpu_time_info { + u32 version; + u32 pad0; + u64 tsc_timestamp; + u64 system_time; + u32 tsc_to_system_mul; + s8 tsc_shift; + u8 flags; + u8 pad[2]; +} __attribute__((__packed__)); /* 32 bytes */ + +struct pvclock_wall_clock { + u32 version; + u32 sec; + u32 nsec; +} __attribute__((__packed__)); + +/* + * These are perodically updated + * xen: magic shared_info page + * kvm: gpa registered via msr + * and then copied here. + */ +struct pvclock_shadow_time { + u64 tsc_timestamp; /* TSC at last update of time vals. */ + u64 system_timestamp; /* Time, in nanosecs, since boot. */ + u32 tsc_to_nsec_mul; + int tsc_shift; + u32 version; + u8 flags; +}; + + +struct timespec { + long tv_sec; + long tv_nsec; +}; + +void pvclock_set_flags(unsigned char flags); +cycle_t kvm_clock_read(); +void kvm_get_wallclock(struct timespec *ts); +void kvm_clock_init(void *data); +void kvm_clock_clear(void *data); + +#endif diff --git a/kvm-unittest/x86/print.h b/kvm-unittest/x86/print.h new file mode 100644 index 0000000..d5bd2f9 --- /dev/null +++ b/kvm-unittest/x86/print.h @@ -0,0 +1,19 @@ +#ifndef PRINT_H +#define PRINT_H + +.macro PRINT text + +.data + +333: .asciz "\text\n" + +.previous + + push %rdi + lea 333b, %rdi + call print + pop %rdi + +.endm + +#endif diff --git a/kvm-unittest/x86/svm.h b/kvm-unittest/x86/svm.h new file mode 100644 index 0000000..3fdc0d3 --- /dev/null +++ b/kvm-unittest/x86/svm.h @@ -0,0 +1,328 @@ +#ifndef __SVM_H +#define __SVM_H + +#include "libcflat.h" + +enum { + INTERCEPT_INTR, + INTERCEPT_NMI, + INTERCEPT_SMI, + INTERCEPT_INIT, + INTERCEPT_VINTR, + INTERCEPT_SELECTIVE_CR0, + INTERCEPT_STORE_IDTR, + INTERCEPT_STORE_GDTR, + INTERCEPT_STORE_LDTR, + INTERCEPT_STORE_TR, + INTERCEPT_LOAD_IDTR, + INTERCEPT_LOAD_GDTR, + INTERCEPT_LOAD_LDTR, + INTERCEPT_LOAD_TR, + INTERCEPT_RDTSC, + INTERCEPT_RDPMC, + INTERCEPT_PUSHF, + INTERCEPT_POPF, + INTERCEPT_CPUID, + INTERCEPT_RSM, + INTERCEPT_IRET, + INTERCEPT_INTn, + INTERCEPT_INVD, + INTERCEPT_PAUSE, + INTERCEPT_HLT, + INTERCEPT_INVLPG, + INTERCEPT_INVLPGA, + INTERCEPT_IOIO_PROT, + INTERCEPT_MSR_PROT, + INTERCEPT_TASK_SWITCH, + INTERCEPT_FERR_FREEZE, + INTERCEPT_SHUTDOWN, + INTERCEPT_VMRUN, + INTERCEPT_VMMCALL, + INTERCEPT_VMLOAD, + INTERCEPT_VMSAVE, + INTERCEPT_STGI, + INTERCEPT_CLGI, + INTERCEPT_SKINIT, + INTERCEPT_RDTSCP, + INTERCEPT_ICEBP, + INTERCEPT_WBINVD, + INTERCEPT_MONITOR, + INTERCEPT_MWAIT, + INTERCEPT_MWAIT_COND, +}; + + +struct __attribute__ ((__packed__)) vmcb_control_area { + u16 intercept_cr_read; + u16 intercept_cr_write; + u16 intercept_dr_read; + u16 intercept_dr_write; + u32 intercept_exceptions; + u64 intercept; + u8 reserved_1[42]; + u16 pause_filter_count; + u64 iopm_base_pa; + u64 msrpm_base_pa; + u64 tsc_offset; + u32 asid; + u8 tlb_ctl; + u8 reserved_2[3]; + u32 int_ctl; + u32 int_vector; + u32 int_state; + u8 reserved_3[4]; + u32 exit_code; + u32 exit_code_hi; + u64 exit_info_1; + u64 exit_info_2; + u32 exit_int_info; + u32 exit_int_info_err; + u64 nested_ctl; + u8 reserved_4[16]; + u32 event_inj; + u32 event_inj_err; + u64 nested_cr3; + u64 lbr_ctl; + u64 reserved_5; + u64 next_rip; + u8 reserved_6[816]; +}; + + +#define TLB_CONTROL_DO_NOTHING 0 +#define TLB_CONTROL_FLUSH_ALL_ASID 1 + +#define V_TPR_MASK 0x0f + +#define V_IRQ_SHIFT 8 +#define V_IRQ_MASK (1 << V_IRQ_SHIFT) + +#define V_INTR_PRIO_SHIFT 16 +#define V_INTR_PRIO_MASK (0x0f << V_INTR_PRIO_SHIFT) + +#define V_IGN_TPR_SHIFT 20 +#define V_IGN_TPR_MASK (1 << V_IGN_TPR_SHIFT) + +#define V_INTR_MASKING_SHIFT 24 +#define V_INTR_MASKING_MASK (1 << V_INTR_MASKING_SHIFT) + +#define SVM_INTERRUPT_SHADOW_MASK 1 + +#define SVM_IOIO_STR_SHIFT 2 +#define SVM_IOIO_REP_SHIFT 3 +#define SVM_IOIO_SIZE_SHIFT 4 +#define SVM_IOIO_ASIZE_SHIFT 7 + +#define SVM_IOIO_TYPE_MASK 1 +#define SVM_IOIO_STR_MASK (1 << SVM_IOIO_STR_SHIFT) +#define SVM_IOIO_REP_MASK (1 << SVM_IOIO_REP_SHIFT) +#define SVM_IOIO_SIZE_MASK (7 << SVM_IOIO_SIZE_SHIFT) +#define SVM_IOIO_ASIZE_MASK (7 << SVM_IOIO_ASIZE_SHIFT) + +#define SVM_VM_CR_VALID_MASK 0x001fULL +#define SVM_VM_CR_SVM_LOCK_MASK 0x0008ULL +#define SVM_VM_CR_SVM_DIS_MASK 0x0010ULL + +struct __attribute__ ((__packed__)) vmcb_seg { + u16 selector; + u16 attrib; + u32 limit; + u64 base; +}; + +struct __attribute__ ((__packed__)) vmcb_save_area { + struct vmcb_seg es; + struct vmcb_seg cs; + struct vmcb_seg ss; + struct vmcb_seg ds; + struct vmcb_seg fs; + struct vmcb_seg gs; + struct vmcb_seg gdtr; + struct vmcb_seg ldtr; + struct vmcb_seg idtr; + struct vmcb_seg tr; + u8 reserved_1[43]; + u8 cpl; + u8 reserved_2[4]; + u64 efer; + u8 reserved_3[112]; + u64 cr4; + u64 cr3; + u64 cr0; + u64 dr7; + u64 dr6; + u64 rflags; + u64 rip; + u8 reserved_4[88]; + u64 rsp; + u8 reserved_5[24]; + u64 rax; + u64 star; + u64 lstar; + u64 cstar; + u64 sfmask; + u64 kernel_gs_base; + u64 sysenter_cs; + u64 sysenter_esp; + u64 sysenter_eip; + u64 cr2; + u8 reserved_6[32]; + u64 g_pat; + u64 dbgctl; + u64 br_from; + u64 br_to; + u64 last_excp_from; + u64 last_excp_to; +}; + +struct __attribute__ ((__packed__)) vmcb { + struct vmcb_control_area control; + struct vmcb_save_area save; +}; + +#define SVM_CPUID_FEATURE_SHIFT 2 +#define SVM_CPUID_FUNC 0x8000000a + +#define SVM_VM_CR_SVM_DISABLE 4 + +#define SVM_SELECTOR_S_SHIFT 4 +#define SVM_SELECTOR_DPL_SHIFT 5 +#define SVM_SELECTOR_P_SHIFT 7 +#define SVM_SELECTOR_AVL_SHIFT 8 +#define SVM_SELECTOR_L_SHIFT 9 +#define SVM_SELECTOR_DB_SHIFT 10 +#define SVM_SELECTOR_G_SHIFT 11 + +#define SVM_SELECTOR_TYPE_MASK (0xf) +#define SVM_SELECTOR_S_MASK (1 << SVM_SELECTOR_S_SHIFT) +#define SVM_SELECTOR_DPL_MASK (3 << SVM_SELECTOR_DPL_SHIFT) +#define SVM_SELECTOR_P_MASK (1 << SVM_SELECTOR_P_SHIFT) +#define SVM_SELECTOR_AVL_MASK (1 << SVM_SELECTOR_AVL_SHIFT) +#define SVM_SELECTOR_L_MASK (1 << SVM_SELECTOR_L_SHIFT) +#define SVM_SELECTOR_DB_MASK (1 << SVM_SELECTOR_DB_SHIFT) +#define SVM_SELECTOR_G_MASK (1 << SVM_SELECTOR_G_SHIFT) + +#define SVM_SELECTOR_WRITE_MASK (1 << 1) +#define SVM_SELECTOR_READ_MASK SVM_SELECTOR_WRITE_MASK +#define SVM_SELECTOR_CODE_MASK (1 << 3) + +#define INTERCEPT_CR0_MASK 1 +#define INTERCEPT_CR3_MASK (1 << 3) +#define INTERCEPT_CR4_MASK (1 << 4) +#define INTERCEPT_CR8_MASK (1 << 8) + +#define INTERCEPT_DR0_MASK 1 +#define INTERCEPT_DR1_MASK (1 << 1) +#define INTERCEPT_DR2_MASK (1 << 2) +#define INTERCEPT_DR3_MASK (1 << 3) +#define INTERCEPT_DR4_MASK (1 << 4) +#define INTERCEPT_DR5_MASK (1 << 5) +#define INTERCEPT_DR6_MASK (1 << 6) +#define INTERCEPT_DR7_MASK (1 << 7) + +#define SVM_EVTINJ_VEC_MASK 0xff + +#define SVM_EVTINJ_TYPE_SHIFT 8 +#define SVM_EVTINJ_TYPE_MASK (7 << SVM_EVTINJ_TYPE_SHIFT) + +#define SVM_EVTINJ_TYPE_INTR (0 << SVM_EVTINJ_TYPE_SHIFT) +#define SVM_EVTINJ_TYPE_NMI (2 << SVM_EVTINJ_TYPE_SHIFT) +#define SVM_EVTINJ_TYPE_EXEPT (3 << SVM_EVTINJ_TYPE_SHIFT) +#define SVM_EVTINJ_TYPE_SOFT (4 << SVM_EVTINJ_TYPE_SHIFT) + +#define SVM_EVTINJ_VALID (1 << 31) +#define SVM_EVTINJ_VALID_ERR (1 << 11) + +#define SVM_EXITINTINFO_VEC_MASK SVM_EVTINJ_VEC_MASK +#define SVM_EXITINTINFO_TYPE_MASK SVM_EVTINJ_TYPE_MASK + +#define SVM_EXITINTINFO_TYPE_INTR SVM_EVTINJ_TYPE_INTR +#define SVM_EXITINTINFO_TYPE_NMI SVM_EVTINJ_TYPE_NMI +#define SVM_EXITINTINFO_TYPE_EXEPT SVM_EVTINJ_TYPE_EXEPT +#define SVM_EXITINTINFO_TYPE_SOFT SVM_EVTINJ_TYPE_SOFT + +#define SVM_EXITINTINFO_VALID SVM_EVTINJ_VALID +#define SVM_EXITINTINFO_VALID_ERR SVM_EVTINJ_VALID_ERR + +#define SVM_EXITINFOSHIFT_TS_REASON_IRET 36 +#define SVM_EXITINFOSHIFT_TS_REASON_JMP 38 +#define SVM_EXITINFOSHIFT_TS_HAS_ERROR_CODE 44 + +#define SVM_EXIT_READ_CR0 0x000 +#define SVM_EXIT_READ_CR3 0x003 +#define SVM_EXIT_READ_CR4 0x004 +#define SVM_EXIT_READ_CR8 0x008 +#define SVM_EXIT_WRITE_CR0 0x010 +#define SVM_EXIT_WRITE_CR3 0x013 +#define SVM_EXIT_WRITE_CR4 0x014 +#define SVM_EXIT_WRITE_CR8 0x018 +#define SVM_EXIT_READ_DR0 0x020 +#define SVM_EXIT_READ_DR1 0x021 +#define SVM_EXIT_READ_DR2 0x022 +#define SVM_EXIT_READ_DR3 0x023 +#define SVM_EXIT_READ_DR4 0x024 +#define SVM_EXIT_READ_DR5 0x025 +#define SVM_EXIT_READ_DR6 0x026 +#define SVM_EXIT_READ_DR7 0x027 +#define SVM_EXIT_WRITE_DR0 0x030 +#define SVM_EXIT_WRITE_DR1 0x031 +#define SVM_EXIT_WRITE_DR2 0x032 +#define SVM_EXIT_WRITE_DR3 0x033 +#define SVM_EXIT_WRITE_DR4 0x034 +#define SVM_EXIT_WRITE_DR5 0x035 +#define SVM_EXIT_WRITE_DR6 0x036 +#define SVM_EXIT_WRITE_DR7 0x037 +#define SVM_EXIT_EXCP_BASE 0x040 +#define SVM_EXIT_INTR 0x060 +#define SVM_EXIT_NMI 0x061 +#define SVM_EXIT_SMI 0x062 +#define SVM_EXIT_INIT 0x063 +#define SVM_EXIT_VINTR 0x064 +#define SVM_EXIT_CR0_SEL_WRITE 0x065 +#define SVM_EXIT_IDTR_READ 0x066 +#define SVM_EXIT_GDTR_READ 0x067 +#define SVM_EXIT_LDTR_READ 0x068 +#define SVM_EXIT_TR_READ 0x069 +#define SVM_EXIT_IDTR_WRITE 0x06a +#define SVM_EXIT_GDTR_WRITE 0x06b +#define SVM_EXIT_LDTR_WRITE 0x06c +#define SVM_EXIT_TR_WRITE 0x06d +#define SVM_EXIT_RDTSC 0x06e +#define SVM_EXIT_RDPMC 0x06f +#define SVM_EXIT_PUSHF 0x070 +#define SVM_EXIT_POPF 0x071 +#define SVM_EXIT_CPUID 0x072 +#define SVM_EXIT_RSM 0x073 +#define SVM_EXIT_IRET 0x074 +#define SVM_EXIT_SWINT 0x075 +#define SVM_EXIT_INVD 0x076 +#define SVM_EXIT_PAUSE 0x077 +#define SVM_EXIT_HLT 0x078 +#define SVM_EXIT_INVLPG 0x079 +#define SVM_EXIT_INVLPGA 0x07a +#define SVM_EXIT_IOIO 0x07b +#define SVM_EXIT_MSR 0x07c +#define SVM_EXIT_TASK_SWITCH 0x07d +#define SVM_EXIT_FERR_FREEZE 0x07e +#define SVM_EXIT_SHUTDOWN 0x07f +#define SVM_EXIT_VMRUN 0x080 +#define SVM_EXIT_VMMCALL 0x081 +#define SVM_EXIT_VMLOAD 0x082 +#define SVM_EXIT_VMSAVE 0x083 +#define SVM_EXIT_STGI 0x084 +#define SVM_EXIT_CLGI 0x085 +#define SVM_EXIT_SKINIT 0x086 +#define SVM_EXIT_RDTSCP 0x087 +#define SVM_EXIT_ICEBP 0x088 +#define SVM_EXIT_WBINVD 0x089 +#define SVM_EXIT_MONITOR 0x08a +#define SVM_EXIT_MWAIT 0x08b +#define SVM_EXIT_MWAIT_COND 0x08c +#define SVM_EXIT_NPF 0x400 + +#define SVM_EXIT_ERR -1 + +#define SVM_CR0_SELECTIVE_MASK (X86_CR0_TS | X86_CR0_MP) + +#endif + diff --git a/kvm-unittest/x86/types.h b/kvm-unittest/x86/types.h new file mode 100644 index 0000000..fd22743 --- /dev/null +++ b/kvm-unittest/x86/types.h @@ -0,0 +1,20 @@ +#ifndef __TYPES_H +#define __TYPES_H + +#define DE_VECTOR 0 +#define DB_VECTOR 1 +#define BP_VECTOR 3 +#define OF_VECTOR 4 +#define BR_VECTOR 5 +#define UD_VECTOR 6 +#define NM_VECTOR 7 +#define DF_VECTOR 8 +#define TS_VECTOR 10 +#define NP_VECTOR 11 +#define SS_VECTOR 12 +#define GP_VECTOR 13 +#define PF_VECTOR 14 +#define MF_VECTOR 16 +#define MC_VECTOR 18 + +#endif diff --git a/kvm-unittest/x86/vmx.h b/kvm-unittest/x86/vmx.h new file mode 100644 index 0000000..28595d8 --- /dev/null +++ b/kvm-unittest/x86/vmx.h @@ -0,0 +1,479 @@ +#ifndef __VMX_H +#define __VMX_H + +#include "libcflat.h" + +struct vmcs { + u32 revision_id; /* vmcs revision identifier */ + u32 abort; /* VMX-abort indicator */ + /* VMCS data */ + char data[0]; +}; + +struct regs { + u64 rax; + u64 rcx; + u64 rdx; + u64 rbx; + u64 cr2; + u64 rbp; + u64 rsi; + u64 rdi; + u64 r8; + u64 r9; + u64 r10; + u64 r11; + u64 r12; + u64 r13; + u64 r14; + u64 r15; + u64 rflags; +}; + +struct vmx_test { + const char *name; + void (*init)(struct vmcs *vmcs); + void (*guest_main)(); + int (*exit_handler)(); + void (*syscall_handler)(u64 syscall_no); + struct regs guest_regs; + struct vmcs *vmcs; + int exits; +}; + +union vmx_basic { + u64 val; + struct { + u32 revision; + u32 size:13, + : 3, + width:1, + dual:1, + type:4, + insouts:1, + ctrl:1; + }; +}; + +union vmx_ctrl_pin { + u64 val; + struct { + u32 set, clr; + }; +}; + +union vmx_ctrl_cpu { + u64 val; + struct { + u32 set, clr; + }; +}; + +union vmx_ctrl_exit { + u64 val; + struct { + u32 set, clr; + }; +}; + +union vmx_ctrl_ent { + u64 val; + struct { + u32 set, clr; + }; +}; + +union vmx_ept_vpid { + u64 val; + struct { + u32:16, + super:2, + : 2, + invept:1, + : 11; + u32 invvpid:1; + }; +}; + +struct descr { + u16 limit; + u64 addr; +}; + +enum Encoding { + /* 16-Bit Control Fields */ + VPID = 0x0000ul, + /* Posted-interrupt notification vector */ + PINV = 0x0002ul, + /* EPTP index */ + EPTP_IDX = 0x0004ul, + + /* 16-Bit Guest State Fields */ + GUEST_SEL_ES = 0x0800ul, + GUEST_SEL_CS = 0x0802ul, + GUEST_SEL_SS = 0x0804ul, + GUEST_SEL_DS = 0x0806ul, + GUEST_SEL_FS = 0x0808ul, + GUEST_SEL_GS = 0x080aul, + GUEST_SEL_LDTR = 0x080cul, + GUEST_SEL_TR = 0x080eul, + GUEST_INT_STATUS = 0x0810ul, + + /* 16-Bit Host State Fields */ + HOST_SEL_ES = 0x0c00ul, + HOST_SEL_CS = 0x0c02ul, + HOST_SEL_SS = 0x0c04ul, + HOST_SEL_DS = 0x0c06ul, + HOST_SEL_FS = 0x0c08ul, + HOST_SEL_GS = 0x0c0aul, + HOST_SEL_TR = 0x0c0cul, + + /* 64-Bit Control Fields */ + IO_BITMAP_A = 0x2000ul, + IO_BITMAP_B = 0x2002ul, + MSR_BITMAP = 0x2004ul, + EXIT_MSR_ST_ADDR = 0x2006ul, + EXIT_MSR_LD_ADDR = 0x2008ul, + ENTER_MSR_LD_ADDR = 0x200aul, + VMCS_EXEC_PTR = 0x200cul, + TSC_OFFSET = 0x2010ul, + TSC_OFFSET_HI = 0x2011ul, + APIC_VIRT_ADDR = 0x2012ul, + APIC_ACCS_ADDR = 0x2014ul, + EPTP = 0x201aul, + EPTP_HI = 0x201bul, + + /* 64-Bit Readonly Data Field */ + INFO_PHYS_ADDR = 0x2400ul, + + /* 64-Bit Guest State */ + VMCS_LINK_PTR = 0x2800ul, + VMCS_LINK_PTR_HI = 0x2801ul, + GUEST_DEBUGCTL = 0x2802ul, + GUEST_DEBUGCTL_HI = 0x2803ul, + GUEST_EFER = 0x2806ul, + GUEST_PERF_GLOBAL_CTRL = 0x2808ul, + GUEST_PDPTE = 0x280aul, + + /* 64-Bit Host State */ + HOST_EFER = 0x2c02ul, + HOST_PERF_GLOBAL_CTRL = 0x2c04ul, + + /* 32-Bit Control Fields */ + PIN_CONTROLS = 0x4000ul, + CPU_EXEC_CTRL0 = 0x4002ul, + EXC_BITMAP = 0x4004ul, + PF_ERROR_MASK = 0x4006ul, + PF_ERROR_MATCH = 0x4008ul, + CR3_TARGET_COUNT = 0x400aul, + EXI_CONTROLS = 0x400cul, + EXI_MSR_ST_CNT = 0x400eul, + EXI_MSR_LD_CNT = 0x4010ul, + ENT_CONTROLS = 0x4012ul, + ENT_MSR_LD_CNT = 0x4014ul, + ENT_INTR_INFO = 0x4016ul, + ENT_INTR_ERROR = 0x4018ul, + ENT_INST_LEN = 0x401aul, + TPR_THRESHOLD = 0x401cul, + CPU_EXEC_CTRL1 = 0x401eul, + + /* 32-Bit R/O Data Fields */ + VMX_INST_ERROR = 0x4400ul, + EXI_REASON = 0x4402ul, + EXI_INTR_INFO = 0x4404ul, + EXI_INTR_ERROR = 0x4406ul, + IDT_VECT_INFO = 0x4408ul, + IDT_VECT_ERROR = 0x440aul, + EXI_INST_LEN = 0x440cul, + EXI_INST_INFO = 0x440eul, + + /* 32-Bit Guest State Fields */ + GUEST_LIMIT_ES = 0x4800ul, + GUEST_LIMIT_CS = 0x4802ul, + GUEST_LIMIT_SS = 0x4804ul, + GUEST_LIMIT_DS = 0x4806ul, + GUEST_LIMIT_FS = 0x4808ul, + GUEST_LIMIT_GS = 0x480aul, + GUEST_LIMIT_LDTR = 0x480cul, + GUEST_LIMIT_TR = 0x480eul, + GUEST_LIMIT_GDTR = 0x4810ul, + GUEST_LIMIT_IDTR = 0x4812ul, + GUEST_AR_ES = 0x4814ul, + GUEST_AR_CS = 0x4816ul, + GUEST_AR_SS = 0x4818ul, + GUEST_AR_DS = 0x481aul, + GUEST_AR_FS = 0x481cul, + GUEST_AR_GS = 0x481eul, + GUEST_AR_LDTR = 0x4820ul, + GUEST_AR_TR = 0x4822ul, + GUEST_INTR_STATE = 0x4824ul, + GUEST_ACTV_STATE = 0x4826ul, + GUEST_SMBASE = 0x4828ul, + GUEST_SYSENTER_CS = 0x482aul, + + /* 32-Bit Host State Fields */ + HOST_SYSENTER_CS = 0x4c00ul, + + /* Natural-Width Control Fields */ + CR0_MASK = 0x6000ul, + CR4_MASK = 0x6002ul, + CR0_READ_SHADOW = 0x6004ul, + CR4_READ_SHADOW = 0x6006ul, + CR3_TARGET_0 = 0x6008ul, + CR3_TARGET_1 = 0x600aul, + CR3_TARGET_2 = 0x600cul, + CR3_TARGET_3 = 0x600eul, + + /* Natural-Width R/O Data Fields */ + EXI_QUALIFICATION = 0x6400ul, + IO_RCX = 0x6402ul, + IO_RSI = 0x6404ul, + IO_RDI = 0x6406ul, + IO_RIP = 0x6408ul, + GUEST_LINEAR_ADDRESS = 0x640aul, + + /* Natural-Width Guest State Fields */ + GUEST_CR0 = 0x6800ul, + GUEST_CR3 = 0x6802ul, + GUEST_CR4 = 0x6804ul, + GUEST_BASE_ES = 0x6806ul, + GUEST_BASE_CS = 0x6808ul, + GUEST_BASE_SS = 0x680aul, + GUEST_BASE_DS = 0x680cul, + GUEST_BASE_FS = 0x680eul, + GUEST_BASE_GS = 0x6810ul, + GUEST_BASE_LDTR = 0x6812ul, + GUEST_BASE_TR = 0x6814ul, + GUEST_BASE_GDTR = 0x6816ul, + GUEST_BASE_IDTR = 0x6818ul, + GUEST_DR7 = 0x681aul, + GUEST_RSP = 0x681cul, + GUEST_RIP = 0x681eul, + GUEST_RFLAGS = 0x6820ul, + GUEST_PENDING_DEBUG = 0x6822ul, + GUEST_SYSENTER_ESP = 0x6824ul, + GUEST_SYSENTER_EIP = 0x6826ul, + + /* Natural-Width Host State Fields */ + HOST_CR0 = 0x6c00ul, + HOST_CR3 = 0x6c02ul, + HOST_CR4 = 0x6c04ul, + HOST_BASE_FS = 0x6c06ul, + HOST_BASE_GS = 0x6c08ul, + HOST_BASE_TR = 0x6c0aul, + HOST_BASE_GDTR = 0x6c0cul, + HOST_BASE_IDTR = 0x6c0eul, + HOST_SYSENTER_ESP = 0x6c10ul, + HOST_SYSENTER_EIP = 0x6c12ul, + HOST_RSP = 0x6c14ul, + HOST_RIP = 0x6c16ul +}; + +enum Reason { + VMX_EXC_NMI = 0, + VMX_EXTINT = 1, + VMX_TRIPLE_FAULT = 2, + VMX_INIT = 3, + VMX_SIPI = 4, + VMX_SMI_IO = 5, + VMX_SMI_OTHER = 6, + VMX_INTR_WINDOW = 7, + VMX_NMI_WINDOW = 8, + VMX_TASK_SWITCH = 9, + VMX_CPUID = 10, + VMX_GETSEC = 11, + VMX_HLT = 12, + VMX_INVD = 13, + VMX_INVLPG = 14, + VMX_RDPMC = 15, + VMX_RDTSC = 16, + VMX_RSM = 17, + VMX_VMCALL = 18, + VMX_VMCLEAR = 19, + VMX_VMLAUNCH = 20, + VMX_VMPTRLD = 21, + VMX_VMPTRST = 22, + VMX_VMREAD = 23, + VMX_VMRESUME = 24, + VMX_VMWRITE = 25, + VMX_VMXOFF = 26, + VMX_VMXON = 27, + VMX_CR = 28, + VMX_DR = 29, + VMX_IO = 30, + VMX_RDMSR = 31, + VMX_WRMSR = 32, + VMX_FAIL_STATE = 33, + VMX_FAIL_MSR = 34, + VMX_MWAIT = 36, + VMX_MTF = 37, + VMX_MONITOR = 39, + VMX_PAUSE = 40, + VMX_FAIL_MCHECK = 41, + VMX_TPR_THRESHOLD = 43, + VMX_APIC_ACCESS = 44, + VMX_GDTR_IDTR = 46, + VMX_LDTR_TR = 47, + VMX_EPT_VIOLATION = 48, + VMX_EPT_MISCONFIG = 49, + VMX_INVEPT = 50, + VMX_PREEMPT = 52, + VMX_INVVPID = 53, + VMX_WBINVD = 54, + VMX_XSETBV = 55 +}; + +#define X86_EFLAGS_CF 0x00000001 /* Carry Flag */ +#define X86_EFLAGS_ZF 0x00000040 /* Zero Flag */ + +enum Ctrl_exi { + EXI_HOST_64 = 1UL << 9, + EXI_LOAD_PERF = 1UL << 12, + EXI_INTA = 1UL << 15, + EXI_LOAD_EFER = 1UL << 21, +}; + +enum Ctrl_ent { + ENT_GUEST_64 = 1UL << 9, + ENT_LOAD_EFER = 1UL << 15, +}; + +enum Ctrl_pin { + PIN_EXTINT = 1ul << 0, + PIN_NMI = 1ul << 3, + PIN_VIRT_NMI = 1ul << 5, +}; + +enum Ctrl0 { + CPU_INTR_WINDOW = 1ul << 2, + CPU_HLT = 1ul << 7, + CPU_INVLPG = 1ul << 9, + CPU_CR3_LOAD = 1ul << 15, + CPU_CR3_STORE = 1ul << 16, + CPU_TPR_SHADOW = 1ul << 21, + CPU_NMI_WINDOW = 1ul << 22, + CPU_IO = 1ul << 24, + CPU_IO_BITMAP = 1ul << 25, + CPU_SECONDARY = 1ul << 31, +}; + +enum Ctrl1 { + CPU_EPT = 1ul << 1, + CPU_VPID = 1ul << 5, + CPU_URG = 1ul << 7, +}; + +#define SAVE_GPR \ + "xchg %rax, regs\n\t" \ + "xchg %rbx, regs+0x8\n\t" \ + "xchg %rcx, regs+0x10\n\t" \ + "xchg %rdx, regs+0x18\n\t" \ + "xchg %rbp, regs+0x28\n\t" \ + "xchg %rsi, regs+0x30\n\t" \ + "xchg %rdi, regs+0x38\n\t" \ + "xchg %r8, regs+0x40\n\t" \ + "xchg %r9, regs+0x48\n\t" \ + "xchg %r10, regs+0x50\n\t" \ + "xchg %r11, regs+0x58\n\t" \ + "xchg %r12, regs+0x60\n\t" \ + "xchg %r13, regs+0x68\n\t" \ + "xchg %r14, regs+0x70\n\t" \ + "xchg %r15, regs+0x78\n\t" + +#define LOAD_GPR SAVE_GPR + +#define SAVE_GPR_C \ + "xchg %%rax, regs\n\t" \ + "xchg %%rbx, regs+0x8\n\t" \ + "xchg %%rcx, regs+0x10\n\t" \ + "xchg %%rdx, regs+0x18\n\t" \ + "xchg %%rbp, regs+0x28\n\t" \ + "xchg %%rsi, regs+0x30\n\t" \ + "xchg %%rdi, regs+0x38\n\t" \ + "xchg %%r8, regs+0x40\n\t" \ + "xchg %%r9, regs+0x48\n\t" \ + "xchg %%r10, regs+0x50\n\t" \ + "xchg %%r11, regs+0x58\n\t" \ + "xchg %%r12, regs+0x60\n\t" \ + "xchg %%r13, regs+0x68\n\t" \ + "xchg %%r14, regs+0x70\n\t" \ + "xchg %%r15, regs+0x78\n\t" + +#define LOAD_GPR_C SAVE_GPR_C + +#define SAVE_RFLAGS \ + "pushf\n\t" \ + "pop host_rflags\n\t" + +#define LOAD_RFLAGS \ + "push host_rflags\n\t" \ + "popf\n\t" + +#define VMX_IO_SIZE_MASK 0x7 +#define _VMX_IO_BYTE 1 +#define _VMX_IO_WORD 2 +#define _VMX_IO_LONG 3 +#define VMX_IO_DIRECTION_MASK (1ul << 3) +#define VMX_IO_IN (1ul << 3) +#define VMX_IO_OUT 0 +#define VMX_IO_STRING (1ul << 4) +#define VMX_IO_REP (1ul << 5) +#define VMX_IO_OPRAND_DX (1ul << 6) +#define VMX_IO_PORT_MASK 0xFFFF0000 +#define VMX_IO_PORT_SHIFT 16 + +#define VMX_TEST_VMEXIT 1 +#define VMX_TEST_EXIT 2 +#define VMX_TEST_RESUME 3 +#define VMX_TEST_LAUNCH_ERR 4 +#define VMX_TEST_RESUME_ERR 5 + +#define HYPERCALL_BIT (1ul << 12) +#define HYPERCALL_MASK 0xFFF +#define HYPERCALL_VMEXIT 0x1 + + +extern struct regs regs; + +extern union vmx_basic basic; +extern union vmx_ctrl_pin ctrl_pin_rev; +extern union vmx_ctrl_cpu ctrl_cpu_rev[2]; +extern union vmx_ctrl_exit ctrl_exit_rev; +extern union vmx_ctrl_ent ctrl_enter_rev; +extern union vmx_ept_vpid ept_vpid; + +static inline int vmcs_clear(struct vmcs *vmcs) +{ + bool ret; + asm volatile ("vmclear %1; setbe %0" : "=q" (ret) : "m" (vmcs) : "cc"); + return ret; +} + +static inline u64 vmcs_read(enum Encoding enc) +{ + u64 val; + asm volatile ("vmread %1, %0" : "=rm" (val) : "r" ((u64)enc) : "cc"); + return val; +} + +static inline int vmcs_write(enum Encoding enc, u64 val) +{ + bool ret; + asm volatile ("vmwrite %1, %2; setbe %0" + : "=q"(ret) : "rm" (val), "r" ((u64)enc) : "cc"); + return ret; +} + +static inline int vmcs_save(struct vmcs **vmcs) +{ + bool ret; + + asm volatile ("vmptrst %1; setbe %0" : "=q" (ret) : "m" (*vmcs) : "cc"); + return ret; +} + +void report(const char *name, int result); +void print_vmexit_info(); + +#endif + diff --git a/kvm-unittest/iotable.c b/kvm-unittest/iotable.c new file mode 100644 index 0000000..91a5016 --- /dev/null +++ b/kvm-unittest/iotable.c @@ -0,0 +1,53 @@ +/* + * Kernel-based Virtual Machine test driver + * + * This test driver provides a simple way of testing kvm, without a full + * device model. + * + * Copyright (C) 2006 Qumranet + * + * Authors: + * + * Avi Kivity + * Yaniv Kamay + * + * This work is licensed under the GNU LGPL license, version 2. + */ + +#include +#include +#include + +#include "iotable.h" + +struct io_table_entry *io_table_lookup(struct io_table *io_table, uint64_t addr) +{ + int i; + + for (i = 0; i < io_table->nr_entries; i++) { + if (io_table->entries[i].start <= addr && + addr < io_table->entries[i].end) + return &io_table->entries[i]; + } + + return NULL; +} + +int io_table_register(struct io_table *io_table, uint64_t start, uint64_t size, + io_table_handler_t *handler, void *opaque) +{ + struct io_table_entry *entry; + + if (io_table->nr_entries == MAX_IO_TABLE) + return -ENOSPC; + + entry = &io_table->entries[io_table->nr_entries]; + io_table->nr_entries++; + + entry->start = start; + entry->end = start + size; + entry->handler = handler; + entry->opaque = opaque; + + return 0; +} diff --git a/kvm-unittest/kvmtrace.c b/kvm-unittest/kvmtrace.c new file mode 100644 index 0000000..de3c189 --- /dev/null +++ b/kvm-unittest/kvmtrace.c @@ -0,0 +1,706 @@ +/* + * kvm tracing application + * + * This tool is used for collecting trace buffer data + * for kvm trace. + * + * Based on blktrace 0.99.3 + * + * Copyright (C) 2005 Jens Axboe + * Copyright (C) 2006 Jens Axboe + * Copyright (C) 2008 Eric Liu + * + * This work is licensed under the GNU LGPL license, version 2. + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __user +#define __user +#endif +#include + +static char kvmtrace_version[] = "0.1"; + +/* + * You may want to increase this even more, if you are logging at a high + * rate and see skipped/missed events + */ +#define BUF_SIZE (512 * 1024) +#define BUF_NR (8) + +#define OFILE_BUF (128 * 1024) + +#define DEBUGFS_TYPE 0x64626720 + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +#define S_OPTS "r:o:w:?Vb:n:D:" +static struct option l_opts[] = { + { + .name = "relay", + .has_arg = required_argument, + .flag = NULL, + .val = 'r' + }, + { + .name = "output", + .has_arg = required_argument, + .flag = NULL, + .val = 'o' + }, + { + .name = "stopwatch", + .has_arg = required_argument, + .flag = NULL, + .val = 'w' + }, + { + .name = "version", + .has_arg = no_argument, + .flag = NULL, + .val = 'V' + }, + { + .name = "buffer-size", + .has_arg = required_argument, + .flag = NULL, + .val = 'b' + }, + { + .name = "num-sub-buffers", + .has_arg = required_argument, + .flag = NULL, + .val = 'n' + }, + { + .name = "output-dir", + .has_arg = required_argument, + .flag = NULL, + .val = 'D' + }, + { + .name = NULL, + } +}; + +struct thread_information { + int cpu; + pthread_t thread; + + int fd; + char fn[MAXPATHLEN + 64]; + + FILE *ofile; + char *ofile_buffer; + + int (*get_subbuf)(struct thread_information *, unsigned int); + int (*read_data)(struct thread_information *, void *, unsigned int); + + unsigned long long data_read; + + struct kvm_trace_information *trace_info; + + int exited; + + /* + * mmap controlled output files + */ + unsigned long long fs_size; + unsigned long long fs_max_size; + unsigned long fs_off; + void *fs_buf; + unsigned long fs_buf_len; + +}; + +struct kvm_trace_information { + int fd; + volatile int trace_started; + unsigned long lost_records; + struct thread_information *threads; + unsigned long buf_size; + unsigned long buf_nr; +}; + +static struct kvm_trace_information trace_information; + +static int ncpus; +static char default_debugfs_path[] = "/sys/kernel/debug"; + +/* command line option globals */ +static char *debugfs_path; +static char *output_name; +static char *output_dir; +static int stop_watch; +static unsigned long buf_size = BUF_SIZE; +static unsigned long buf_nr = BUF_NR; +static unsigned int page_size; + +#define for_each_cpu_online(cpu) \ + for (cpu = 0; cpu < ncpus; cpu++) +#define for_each_tip(tip, i) \ + for (i = 0, tip = trace_information.threads; i < ncpus; i++, tip++) + +#define is_done() (*(volatile int *)(&done)) +static volatile int done; + +#define is_trace_stopped() (*(volatile int *)(&trace_stopped)) +static volatile int trace_stopped; + +static void exit_trace(int status); + +static void handle_sigint(__attribute__((__unused__)) int sig) +{ + ioctl(trace_information.fd, KVM_TRACE_PAUSE); + done = 1; +} + +static int get_lost_records() +{ + int fd; + char tmp[MAXPATHLEN + 64]; + + snprintf(tmp, sizeof(tmp), "%s/kvm/lost_records", debugfs_path); + fd = open(tmp, O_RDONLY); + if (fd < 0) { + /* + * this may be ok, if the kernel doesn't support dropped counts + */ + if (errno == ENOENT) + return 0; + + fprintf(stderr, "Couldn't open dropped file %s\n", tmp); + return -1; + } + + if (read(fd, tmp, sizeof(tmp)) < 0) { + perror(tmp); + close(fd); + return -1; + } + close(fd); + + return atoi(tmp); +} + +static void wait_for_data(struct thread_information *tip, int timeout) +{ + struct pollfd pfd = { .fd = tip->fd, .events = POLLIN }; + + while (!is_done()) { + if (poll(&pfd, 1, timeout) < 0) { + perror("poll"); + break; + } + if (pfd.revents & POLLIN) + break; + } +} + +static int read_data(struct thread_information *tip, void *buf, + unsigned int len) +{ + int ret = 0; + + do { + wait_for_data(tip, 100); + + ret = read(tip->fd, buf, len); + + if (!ret) + continue; + else if (ret > 0) + return ret; + else { + if (errno != EAGAIN) { + perror(tip->fn); + fprintf(stderr, "Thread %d failed read of %s\n", + tip->cpu, tip->fn); + break; + } + continue; + } + } while (!is_done()); + + return ret; + +} + +/* + * For file output, truncate and mmap the file appropriately + */ +static int mmap_subbuf(struct thread_information *tip, unsigned int maxlen) +{ + int ofd = fileno(tip->ofile); + int ret; + unsigned long nr; + unsigned long size; + + /* + * extend file, if we have to. use chunks of 16 subbuffers. + */ + if (tip->fs_off + maxlen > tip->fs_buf_len) { + if (tip->fs_buf) { + munlock(tip->fs_buf, tip->fs_buf_len); + munmap(tip->fs_buf, tip->fs_buf_len); + tip->fs_buf = NULL; + } + + tip->fs_off = tip->fs_size & (page_size - 1); + nr = max(16, tip->trace_info->buf_nr); + size = tip->trace_info->buf_size; + tip->fs_buf_len = (nr * size) - tip->fs_off; + tip->fs_max_size += tip->fs_buf_len; + + if (ftruncate(ofd, tip->fs_max_size) < 0) { + perror("ftruncate"); + return -1; + } + + tip->fs_buf = mmap(NULL, tip->fs_buf_len, PROT_WRITE, + MAP_SHARED, ofd, tip->fs_size - tip->fs_off); + if (tip->fs_buf == MAP_FAILED) { + perror("mmap"); + return -1; + } + mlock(tip->fs_buf, tip->fs_buf_len); + } + + ret = tip->read_data(tip, tip->fs_buf + tip->fs_off, maxlen); + if (ret >= 0) { + tip->data_read += ret; + tip->fs_size += ret; + tip->fs_off += ret; + return 0; + } + + return -1; +} + +static void tip_ftrunc_final(struct thread_information *tip) +{ + /* + * truncate to right size and cleanup mmap + */ + if (tip->ofile) { + int ofd = fileno(tip->ofile); + + if (tip->fs_buf) + munmap(tip->fs_buf, tip->fs_buf_len); + + ftruncate(ofd, tip->fs_size); + } +} + +static void *thread_main(void *arg) +{ + struct thread_information *tip = arg; + pid_t pid = getpid(); + cpu_set_t cpu_mask; + + CPU_ZERO(&cpu_mask); + CPU_SET((tip->cpu), &cpu_mask); + + if (sched_setaffinity(pid, sizeof(cpu_mask), &cpu_mask) == -1) { + perror("sched_setaffinity"); + exit_trace(1); + } + + snprintf(tip->fn, sizeof(tip->fn), "%s/kvm/trace%d", + debugfs_path, tip->cpu); + tip->fd = open(tip->fn, O_RDONLY); + if (tip->fd < 0) { + perror(tip->fn); + fprintf(stderr, "Thread %d failed open of %s\n", tip->cpu, + tip->fn); + exit_trace(1); + } + while (!is_done()) { + if (tip->get_subbuf(tip, tip->trace_info->buf_size) < 0) + break; + } + + /* + * trace is stopped, pull data until we get a short read + */ + while (tip->get_subbuf(tip, tip->trace_info->buf_size) > 0) + ; + + tip_ftrunc_final(tip); + tip->exited = 1; + return NULL; +} + +static int fill_ofname(struct thread_information *tip, char *dst) +{ + struct stat sb; + int len = 0; + + if (output_dir) + len = sprintf(dst, "%s/", output_dir); + else + len = sprintf(dst, "./"); + + if (stat(dst, &sb) < 0) { + if (errno != ENOENT) { + perror("stat"); + return 1; + } + if (mkdir(dst, 0755) < 0) { + perror(dst); + fprintf(stderr, "Can't make output dir\n"); + return 1; + } + } + + sprintf(dst + len, "%s.kvmtrace.%d", output_name, tip->cpu); + + return 0; +} + +static void fill_ops(struct thread_information *tip) +{ + tip->get_subbuf = mmap_subbuf; + tip->read_data = read_data; +} + +static void close_thread(struct thread_information *tip) +{ + if (tip->fd != -1) + close(tip->fd); + if (tip->ofile) + fclose(tip->ofile); + if (tip->ofile_buffer) + free(tip->ofile_buffer); + + tip->fd = -1; + tip->ofile = NULL; + tip->ofile_buffer = NULL; +} + +static int tip_open_output(struct thread_information *tip) +{ + int mode, vbuf_size; + char op[NAME_MAX]; + + if (fill_ofname(tip, op)) + return 1; + + tip->ofile = fopen(op, "w+"); + mode = _IOFBF; + vbuf_size = OFILE_BUF; + + if (tip->ofile == NULL) { + perror(op); + return 1; + } + + tip->ofile_buffer = malloc(vbuf_size); + if (setvbuf(tip->ofile, tip->ofile_buffer, mode, vbuf_size)) { + perror("setvbuf"); + close_thread(tip); + return 1; + } + + fill_ops(tip); + return 0; +} + +static int start_threads(int cpu) +{ + struct thread_information *tip; + + tip = trace_information.threads + cpu; + tip->cpu = cpu; + tip->trace_info = &trace_information; + tip->fd = -1; + + if (tip_open_output(tip)) + return 1; + + if (pthread_create(&tip->thread, NULL, thread_main, tip)) { + perror("pthread_create"); + close_thread(tip); + return 1; + } + + return 0; +} + +static void stop_threads() +{ + struct thread_information *tip; + unsigned long ret; + int i; + + for_each_tip(tip, i) { + if (tip->thread) + (void) pthread_join(tip->thread, (void *) &ret); + close_thread(tip); + } +} + +static int start_trace(void) +{ + int fd; + struct kvm_user_trace_setup kuts; + + fd = trace_information.fd = open("/dev/kvm", O_RDWR); + if (fd == -1) { + perror("/dev/kvm"); + return 1; + } + + memset(&kuts, 0, sizeof(kuts)); + kuts.buf_size = trace_information.buf_size = buf_size; + kuts.buf_nr = trace_information.buf_nr = buf_nr; + + if (ioctl(trace_information.fd , KVM_TRACE_ENABLE, &kuts) < 0) { + perror("KVM_TRACE_ENABLE"); + close(fd); + return 1; + } + trace_information.trace_started = 1; + + return 0; +} + +static void cleanup_trace(void) +{ + if (trace_information.fd == -1) + return; + + trace_information.lost_records = get_lost_records(); + + if (trace_information.trace_started) { + trace_information.trace_started = 0; + if (ioctl(trace_information.fd, KVM_TRACE_DISABLE) < 0) + perror("KVM_TRACE_DISABLE"); + } + + close(trace_information.fd); + trace_information.fd = -1; +} + +static void stop_all_traces(void) +{ + if (!is_trace_stopped()) { + trace_stopped = 1; + stop_threads(); + cleanup_trace(); + } +} + +static void exit_trace(int status) +{ + stop_all_traces(); + exit(status); +} + +static int start_kvm_trace(void) +{ + int i, size; + struct thread_information *tip; + + size = ncpus * sizeof(struct thread_information); + tip = malloc(size); + if (!tip) { + fprintf(stderr, "Out of memory, threads (%d)\n", size); + return 1; + } + memset(tip, 0, size); + trace_information.threads = tip; + + if (start_trace()) + return 1; + + for_each_cpu_online(i) { + if (start_threads(i)) { + fprintf(stderr, "Failed to start worker threads\n"); + break; + } + } + + if (i != ncpus) { + stop_threads(); + cleanup_trace(); + return 1; + } + + return 0; +} + +static void wait_for_threads(void) +{ + struct thread_information *tip; + int i, tips_running; + + do { + tips_running = 0; + usleep(100000); + + for_each_tip(tip, i) + tips_running += !tip->exited; + + } while (tips_running); +} + +static void show_stats(void) +{ + struct thread_information *tip; + unsigned long long data_read; + int i; + + data_read = 0; + for_each_tip(tip, i) { + printf(" CPU%3d: %8llu KiB data\n", + tip->cpu, (tip->data_read + 1023) >> 10); + data_read += tip->data_read; + } + + printf(" Total: lost %lu, %8llu KiB data\n", + trace_information.lost_records, (data_read + 1023) >> 10); + + if (trace_information.lost_records) + fprintf(stderr, "You have lost records, " + "consider using a larger buffer size (-b)\n"); +} + +static char usage_str[] = \ + "[ -r debugfs path ] [ -D output dir ] [ -b buffer size ]\n" \ + "[ -n number of buffers] [ -o ] [ -w time ] [ -V ]\n\n" \ + "\t-r Path to mounted debugfs, defaults to /sys/kernel/debug\n" \ + "\t-o File(s) to send output to\n" \ + "\t-D Directory to prepend to output file names\n" \ + "\t-w Stop after defined time, in seconds\n" \ + "\t-b Sub buffer size in KiB\n" \ + "\t-n Number of sub buffers\n" \ + "\t-V Print program version info\n\n"; + +static void show_usage(char *prog) +{ + fprintf(stderr, "Usage: %s %s %s", prog, kvmtrace_version, usage_str); + exit(EXIT_FAILURE); +} + +void parse_args(int argc, char **argv) +{ + int c; + + while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) >= 0) { + switch (c) { + case 'r': + debugfs_path = optarg; + break; + case 'o': + output_name = optarg; + break; + case 'w': + stop_watch = atoi(optarg); + if (stop_watch <= 0) { + fprintf(stderr, + "Invalid stopwatch value (%d secs)\n", + stop_watch); + exit(EXIT_FAILURE); + } + break; + case 'V': + printf("%s version %s\n", argv[0], kvmtrace_version); + exit(EXIT_SUCCESS); + case 'b': + buf_size = strtoul(optarg, NULL, 10); + if (buf_size <= 0 || buf_size > 16*1024) { + fprintf(stderr, + "Invalid buffer size (%lu)\n", + buf_size); + exit(EXIT_FAILURE); + } + buf_size <<= 10; + break; + case 'n': + buf_nr = strtoul(optarg, NULL, 10); + if (buf_nr <= 0) { + fprintf(stderr, + "Invalid buffer nr (%lu)\n", buf_nr); + exit(EXIT_FAILURE); + } + break; + case 'D': + output_dir = optarg; + break; + default: + show_usage(argv[0]); + } + } + + if (optind < argc || output_name == NULL) + show_usage(argv[0]); +} + +int main(int argc, char *argv[]) +{ + struct statfs st; + + parse_args(argc, argv); + + if (!debugfs_path) + debugfs_path = default_debugfs_path; + + if (statfs(debugfs_path, &st) < 0) { + perror("statfs"); + fprintf(stderr, "%s does not appear to be a valid path\n", + debugfs_path); + return 1; + } else if (st.f_type != (long) DEBUGFS_TYPE) { + fprintf(stderr, "%s does not appear to be a debug filesystem," + " please mount debugfs.\n", + debugfs_path); + return 1; + } + + page_size = getpagesize(); + + ncpus = sysconf(_SC_NPROCESSORS_ONLN); + if (ncpus < 0) { + fprintf(stderr, "sysconf(_SC_NPROCESSORS_ONLN) failed\n"); + return 1; + } + + signal(SIGINT, handle_sigint); + signal(SIGHUP, handle_sigint); + signal(SIGTERM, handle_sigint); + signal(SIGALRM, handle_sigint); + signal(SIGPIPE, SIG_IGN); + + if (start_kvm_trace() != 0) + return 1; + + if (stop_watch) + alarm(stop_watch); + + wait_for_threads(); + stop_all_traces(); + show_stats(); + + return 0; +} diff --git a/kvm-unittest/lib/argv.c b/kvm-unittest/lib/argv.c new file mode 100644 index 0000000..4ee54a6 --- /dev/null +++ b/kvm-unittest/lib/argv.c @@ -0,0 +1,33 @@ +#include "libcflat.h" + +int __argc; +char *__argv[100]; +char *__args; +char __args_copy[1000]; + +static bool isblank(char p) +{ + return p == ' ' || p == '\t'; +} + +static char *skip_blanks(char *p) +{ + while (isblank(*p)) + ++p; + return p; +} + +void __setup_args(void) +{ + char *args = __args; + char **argv = __argv; + char *p = __args_copy; + + while (*(args = skip_blanks(args)) != '\0') { + *argv++ = p; + while (*args != '\0' && !isblank(*args)) + *p++ = *args++; + *p++ = '\0'; + } + __argc = argv - __argv; +} diff --git a/kvm-unittest/lib/fwcfg.c b/kvm-unittest/lib/fwcfg.c new file mode 100644 index 0000000..dc34d29 --- /dev/null +++ b/kvm-unittest/lib/fwcfg.c @@ -0,0 +1,58 @@ + +void qemu_cfg_select(int f) +{ + outw(QEMU_CFG_CTL_PORT, f); +} + +int qemu_cfg_port_probe() +{ + char *sig = "QEMU"; + int i; + + qemu_cfg_select(QEMU_CFG_SIGNATURE); + + for (i = 0; i < 4; i++) + if (inb(QEMU_CFG_DATA_PORT) != sig[i]) + return 0; + + return 1; +} + +void qemu_cfg_read(uint8_t *buf, int len) +{ + while (len--) + *(buf++) = inb(QEMU_CFG_DATA_PORT); +} + +uint8_t qemu_cfg_get8(void) +{ + uint8_t ret; + + qemu_cfg_read(&ret, 1); + return ret; +} + +uint16_t qemu_cfg_get16(void) +{ + uint16_t ret; + + qemu_cfg_read((uint8_t*)&ret, 2); + return le16_to_cpu(ret); +} + +uint64_t qemu_cfg_get32(void) +{ + uint32_t ret; + + qemu_cfg_read((uint8_t*)&ret, 4); + return le32_to_cpu(ret); +} + +uint64_t qemu_cfg_get64(void) +{ + uint64_t ret; + + qemu_cfg_read((uint8_t*)&ret, 8); + return le64_to_cpu(ret); +} + diff --git a/kvm-unittest/lib/panic.c b/kvm-unittest/lib/panic.c new file mode 100644 index 0000000..6e0b29e --- /dev/null +++ b/kvm-unittest/lib/panic.c @@ -0,0 +1,13 @@ +#include "libcflat.h" + +void panic(char *fmt, ...) +{ + va_list va; + char buf[2000]; + + va_start(va, fmt); + vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + puts(buf); + exit(-1); +} diff --git a/kvm-unittest/lib/powerpc/44x/map.c b/kvm-unittest/lib/powerpc/44x/map.c new file mode 100644 index 0000000..113434d --- /dev/null +++ b/kvm-unittest/lib/powerpc/44x/map.c @@ -0,0 +1,51 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +#include "libcflat.h" + +#define TLB_SIZE 64 + +extern void tlbwe(unsigned int index, + unsigned char tid, + unsigned int word0, + unsigned int word1, + unsigned int word2); + +unsigned int next_free_index; + +#define PAGE_SHIFT 12 +#define PAGE_MASK (~((1<= TLB_SIZE) + panic("TLB overflow"); + + w0 = (vaddr & PAGE_MASK) | V; + w1 = paddr & PAGE_MASK; + w2 = 0x3; + + tlbwe(next_free_index, 0, w0, w1, w2); +} diff --git a/kvm-unittest/lib/powerpc/io.c b/kvm-unittest/lib/powerpc/io.c new file mode 100644 index 0000000..8bd2395 --- /dev/null +++ b/kvm-unittest/lib/powerpc/io.c @@ -0,0 +1,35 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +#include "libcflat.h" + +#define BASE 0xf0000000 +#define _putc ((volatile char *)(BASE)) +#define _exit ((volatile char *)(BASE+1)) + +void puts(const char *s) +{ + while (*s != '\0') + *_putc = *s++; +} + +void exit(int code) +{ + *_exit = code; +} diff --git a/kvm-unittest/lib/printf.c b/kvm-unittest/lib/printf.c new file mode 100644 index 0000000..867eb19 --- /dev/null +++ b/kvm-unittest/lib/printf.c @@ -0,0 +1,182 @@ +#include "libcflat.h" + +typedef struct pstream { + char *buffer; + int remain; + int added; +} pstream_t; + +static void addchar(pstream_t *p, char c) +{ + if (p->remain) { + *p->buffer++ = c; + --p->remain; + } + ++p->added; +} + +void print_str(pstream_t *p, const char *s) +{ + while (*s) + addchar(p, *s++); +} + +static char digits[16] = "0123456789abcdef"; + +void print_int(pstream_t *ps, long long n, int base) +{ + char buf[sizeof(long) * 3 + 2], *p = buf; + int s = 0, i; + + if (n < 0) { + n = -n; + s = 1; + } + + while (n) { + *p++ = digits[n % base]; + n /= base; + } + + if (s) + *p++ = '-'; + + if (p == buf) + *p++ = '0'; + + for (i = 0; i < (p - buf) / 2; ++i) { + char tmp; + + tmp = buf[i]; + buf[i] = p[-1-i]; + p[-1-i] = tmp; + } + + *p = 0; + + print_str(ps, buf); +} + +void print_unsigned(pstream_t *ps, unsigned long long n, int base) +{ + char buf[sizeof(long) * 3 + 1], *p = buf; + int i; + + while (n) { + *p++ = digits[n % base]; + n /= base; + } + + if (p == buf) + *p++ = '0'; + + for (i = 0; i < (p - buf) / 2; ++i) { + char tmp; + + tmp = buf[i]; + buf[i] = p[-1-i]; + p[-1-i] = tmp; + } + + *p = 0; + + print_str(ps, buf); +} + +int vsnprintf(char *buf, int size, const char *fmt, va_list va) +{ + pstream_t s; + + s.buffer = buf; + s.remain = size - 1; + s.added = 0; + while (*fmt) { + char f = *fmt++; + int nlong = 0; + + if (f != '%') { + addchar(&s, f); + continue; + } + morefmt: + f = *fmt++; + switch (f) { + case '%': + addchar(&s, '%'); + break; + case 'c': + addchar(&s, va_arg(va, int)); + break; + case '\0': + --fmt; + break; + case 'l': + ++nlong; + goto morefmt; + case 'd': + switch (nlong) { + case 0: + print_int(&s, va_arg(va, int), 10); + break; + case 1: + print_int(&s, va_arg(va, long), 10); + break; + default: + print_int(&s, va_arg(va, long long), 10); + break; + } + break; + case 'x': + switch (nlong) { + case 0: + print_unsigned(&s, va_arg(va, unsigned), 16); + break; + case 1: + print_unsigned(&s, va_arg(va, unsigned long), 16); + break; + default: + print_unsigned(&s, va_arg(va, unsigned long long), 16); + break; + } + break; + case 'p': + print_str(&s, "0x"); + print_unsigned(&s, (unsigned long)va_arg(va, void *), 16); + break; + case 's': + print_str(&s, va_arg(va, const char *)); + break; + default: + addchar(&s, f); + break; + } + } + *s.buffer = 0; + ++s.added; + return s.added; +} + + +int snprintf(char *buf, int size, const char *fmt, ...) +{ + va_list va; + int r; + + va_start(va, fmt); + r = vsnprintf(buf, size, fmt, va); + va_end(va); + return r; +} + +int printf(const char *fmt, ...) +{ + va_list va; + char buf[2000]; + int r; + + va_start(va, fmt); + r = vsnprintf(buf, sizeof buf, fmt, va); + va_end(va); + puts(buf); + return r; +} diff --git a/kvm-unittest/lib/string.c b/kvm-unittest/lib/string.c new file mode 100644 index 0000000..3a9caf7 --- /dev/null +++ b/kvm-unittest/lib/string.c @@ -0,0 +1,86 @@ +#include "libcflat.h" + +unsigned long strlen(const char *buf) +{ + unsigned long len = 0; + + while (*buf++) + ++len; + return len; +} + +char *strcat(char *dest, const char *src) +{ + char *p = dest; + + while (*p) + ++p; + while ((*p++ = *src++) != 0) + ; + return dest; +} + +int strcmp(const char *a, const char *b) +{ + while (*a == *b) { + if (*a == '\0') { + break; + } + ++a, ++b; + } + return *a - *b; +} + +void *memset(void *s, int c, size_t n) +{ + size_t i; + char *a = s; + + for (i = 0; i < n; ++i) + a[i] = c; + + return s; +} + +void *memcpy(void *dest, const void *src, size_t n) +{ + size_t i; + char *a = dest; + const char *b = src; + + for (i = 0; i < n; ++i) + a[i] = b[i]; + + return dest; +} + +long atol(const char *ptr) +{ + long acc = 0; + const char *s = ptr; + int neg, c; + + while (*s == ' ' || *s == '\t') + s++; + if (*s == '-'){ + neg = 1; + s++; + } else { + neg = 0; + if (*s == '+') + s++; + } + + while (*s) { + if (*s < '0' || *s > '9') + break; + c = *s - '0'; + acc = acc * 10 + c; + s++; + } + + if (neg) + acc = -acc; + + return acc; +} diff --git a/kvm-unittest/lib/x86/apic.c b/kvm-unittest/lib/x86/apic.c new file mode 100644 index 0000000..7bb98ed --- /dev/null +++ b/kvm-unittest/lib/x86/apic.c @@ -0,0 +1,143 @@ +#include "libcflat.h" +#include "apic.h" + +static void *g_apic = (void *)0xfee00000; +static void *g_ioapic = (void *)0xfec00000; + +struct apic_ops { + u32 (*reg_read)(unsigned reg); + void (*reg_write)(unsigned reg, u32 val); + void (*icr_write)(u32 val, u32 dest); + u32 (*id)(void); +}; + +static void outb(unsigned char data, unsigned short port) +{ + asm volatile ("out %0, %1" : : "a"(data), "d"(port)); +} + +static u32 xapic_read(unsigned reg) +{ + return *(volatile u32 *)(g_apic + reg); +} + +static void xapic_write(unsigned reg, u32 val) +{ + *(volatile u32 *)(g_apic + reg) = val; +} + +static void xapic_icr_write(u32 val, u32 dest) +{ + while (xapic_read(APIC_ICR) & APIC_ICR_BUSY) + ; + xapic_write(APIC_ICR2, dest << 24); + xapic_write(APIC_ICR, val); +} + +static uint32_t xapic_id(void) +{ + return xapic_read(APIC_ID) >> 24; +} + +static const struct apic_ops xapic_ops = { + .reg_read = xapic_read, + .reg_write = xapic_write, + .icr_write = xapic_icr_write, + .id = xapic_id, +}; + +static const struct apic_ops *apic_ops = &xapic_ops; + +static u32 x2apic_read(unsigned reg) +{ + unsigned a, d; + + asm volatile ("rdmsr" : "=a"(a), "=d"(d) : "c"(APIC_BASE_MSR + reg/16)); + return a | (u64)d << 32; +} + +static void x2apic_write(unsigned reg, u32 val) +{ + asm volatile ("wrmsr" : : "a"(val), "d"(0), "c"(APIC_BASE_MSR + reg/16)); +} + +static void x2apic_icr_write(u32 val, u32 dest) +{ + asm volatile ("wrmsr" : : "a"(val), "d"(dest), + "c"(APIC_BASE_MSR + APIC_ICR/16)); +} + +static uint32_t x2apic_id(void) +{ + return xapic_read(APIC_ID); +} + +static const struct apic_ops x2apic_ops = { + .reg_read = x2apic_read, + .reg_write = x2apic_write, + .icr_write = x2apic_icr_write, + .id = x2apic_id, +}; + +u32 apic_read(unsigned reg) +{ + return apic_ops->reg_read(reg); +} + +void apic_write(unsigned reg, u32 val) +{ + apic_ops->reg_write(reg, val); +} + +void apic_icr_write(u32 val, u32 dest) +{ + apic_ops->icr_write(val, dest); +} + +uint32_t apic_id(void) +{ + return apic_ops->id(); +} + +#define MSR_APIC_BASE 0x0000001b + +int enable_x2apic(void) +{ + unsigned a, b, c, d; + + asm ("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d) : "0"(1)); + + if (c & (1 << 21)) { + asm ("rdmsr" : "=a"(a), "=d"(d) : "c"(MSR_APIC_BASE)); + a |= 1 << 10; + asm ("wrmsr" : : "a"(a), "d"(d), "c"(MSR_APIC_BASE)); + apic_ops = &x2apic_ops; + return 1; + } else { + return 0; + } +} + +void ioapic_write_reg(unsigned reg, u32 value) +{ + *(volatile u32 *)g_ioapic = reg; + *(volatile u32 *)(g_ioapic + 0x10) = value; +} + +void ioapic_write_redir(unsigned line, ioapic_redir_entry_t e) +{ + ioapic_write_reg(0x10 + line * 2 + 0, ((u32 *)&e)[0]); + ioapic_write_reg(0x10 + line * 2 + 1, ((u32 *)&e)[1]); +} + +void enable_apic(void) +{ + printf("enabling apic\n"); + xapic_write(0xf0, 0x1ff); /* spurious vector register */ +} + +void mask_pic_interrupts(void) +{ + outb(0xff, 0x21); + outb(0xff, 0xa1); +} diff --git a/kvm-unittest/lib/x86/atomic.c b/kvm-unittest/lib/x86/atomic.c new file mode 100644 index 0000000..da74ff2 --- /dev/null +++ b/kvm-unittest/lib/x86/atomic.c @@ -0,0 +1,37 @@ +#include +#include "atomic.h" + +#ifdef __i386__ + +u64 atomic64_cmpxchg(atomic64_t *v, u64 old, u64 new) +{ + u32 low = new; + u32 high = new >> 32; + + asm volatile("lock cmpxchg8b %1\n" + : "+A" (old), + "+m" (*(volatile long long *)&v->counter) + : "b" (low), "c" (high) + : "memory" + ); + + return old; +} + +#else + +u64 atomic64_cmpxchg(atomic64_t *v, u64 old, u64 new) +{ + u64 ret; + u64 _old = old; + u64 _new = new; + + asm volatile("lock cmpxchgq %2,%1" + : "=a" (ret), "+m" (*(volatile long *)&v->counter) + : "r" (_new), "0" (_old) + : "memory" + ); + return ret; +} + +#endif diff --git a/kvm-unittest/lib/x86/desc.c b/kvm-unittest/lib/x86/desc.c new file mode 100644 index 0000000..7c5c721 --- /dev/null +++ b/kvm-unittest/lib/x86/desc.c @@ -0,0 +1,355 @@ +#include "libcflat.h" +#include "desc.h" +#include "processor.h" + +typedef struct { + unsigned short offset0; + unsigned short selector; + unsigned short ist : 3; + unsigned short : 5; + unsigned short type : 4; + unsigned short : 1; + unsigned short dpl : 2; + unsigned short p : 1; + unsigned short offset1; +#ifdef __x86_64__ + unsigned offset2; + unsigned reserved; +#endif +} idt_entry_t; + +typedef struct { + u16 limit_low; + u16 base_low; + u8 base_middle; + u8 access; + u8 granularity; + u8 base_high; +} gdt_entry_t; + +extern idt_entry_t boot_idt[256]; + +void set_idt_entry(int vec, void *addr, int dpl) +{ + idt_entry_t *e = &boot_idt[vec]; + memset(e, 0, sizeof *e); + e->offset0 = (unsigned long)addr; + e->selector = read_cs(); + e->ist = 0; + e->type = 14; + e->dpl = dpl; + e->p = 1; + e->offset1 = (unsigned long)addr >> 16; +#ifdef __x86_64__ + e->offset2 = (unsigned long)addr >> 32; +#endif +} + +void set_idt_sel(int vec, u16 sel) +{ + idt_entry_t *e = &boot_idt[vec]; + e->selector = sel; +} + +struct ex_record { + unsigned long rip; + unsigned long handler; +}; + +extern struct ex_record exception_table_start, exception_table_end; + +static void check_exception_table(struct ex_regs *regs) +{ + struct ex_record *ex; + unsigned ex_val; + + ex_val = regs->vector | (regs->error_code << 16); + + asm("mov %0, %%gs:4" : : "r"(ex_val)); + + for (ex = &exception_table_start; ex != &exception_table_end; ++ex) { + if (ex->rip == regs->rip) { + regs->rip = ex->handler; + return; + } + } + printf("unhandled excecption %d\n", regs->vector); + exit(7); +} + +static void (*exception_handlers[32])(struct ex_regs *regs); + + +void handle_exception(u8 v, void (*func)(struct ex_regs *regs)) +{ + if (v < 32) + exception_handlers[v] = func; +} + +#ifndef __x86_64__ +__attribute__((regparm(1))) +#endif +void do_handle_exception(struct ex_regs *regs) +{ + if (regs->vector < 32 && exception_handlers[regs->vector]) { + exception_handlers[regs->vector](regs); + return; + } + printf("unhandled cpu excecption %d\n", regs->vector); + if (regs->vector == 14) + printf("PF at %p addr %p\n", regs->rip, read_cr2()); + exit(7); +} + +#ifdef __x86_64__ +# define R "r" +# define W "q" +# define S "8" +#else +# define R "e" +# define W "l" +# define S "4" +#endif + +#define EX(NAME, N) extern char NAME##_fault; \ + asm (".pushsection .text \n\t" \ + #NAME"_fault: \n\t" \ + "push"W" $0 \n\t" \ + "push"W" $"#N" \n\t" \ + "jmp __handle_exception \n\t" \ + ".popsection") + +#define EX_E(NAME, N) extern char NAME##_fault; \ + asm (".pushsection .text \n\t" \ + #NAME"_fault: \n\t" \ + "push"W" $"#N" \n\t" \ + "jmp __handle_exception \n\t" \ + ".popsection") + +EX(de, 0); +EX(db, 1); +EX(nmi, 2); +EX(bp, 3); +EX(of, 4); +EX(br, 5); +EX(ud, 6); +EX(nm, 7); +EX_E(df, 8); +EX_E(ts, 10); +EX_E(np, 11); +EX_E(ss, 12); +EX_E(gp, 13); +EX_E(pf, 14); +EX(mf, 16); +EX_E(ac, 17); +EX(mc, 18); +EX(xm, 19); + +asm (".pushsection .text \n\t" + "__handle_exception: \n\t" +#ifdef __x86_64__ + "push %r15; push %r14; push %r13; push %r12 \n\t" + "push %r11; push %r10; push %r9; push %r8 \n\t" +#endif + "push %"R"di; push %"R"si; push %"R"bp; sub $"S", %"R"sp \n\t" + "push %"R"bx; push %"R"dx; push %"R"cx; push %"R"ax \n\t" +#ifdef __x86_64__ + "mov %"R"sp, %"R"di \n\t" +#else + "mov %"R"sp, %"R"ax \n\t" +#endif + "call do_handle_exception \n\t" + "pop %"R"ax; pop %"R"cx; pop %"R"dx; pop %"R"bx \n\t" + "add $"S", %"R"sp; pop %"R"bp; pop %"R"si; pop %"R"di \n\t" +#ifdef __x86_64__ + "pop %r8; pop %r9; pop %r10; pop %r11 \n\t" + "pop %r12; pop %r13; pop %r14; pop %r15 \n\t" +#endif + "add $"S", %"R"sp \n\t" + "add $"S", %"R"sp \n\t" + "iret"W" \n\t" + ".popsection"); + +static void *idt_handlers[32] = { + [0] = &de_fault, + [1] = &db_fault, + [2] = &nmi_fault, + [3] = &bp_fault, + [4] = &of_fault, + [5] = &br_fault, + [6] = &ud_fault, + [7] = &nm_fault, + [8] = &df_fault, + [10] = &ts_fault, + [11] = &np_fault, + [12] = &ss_fault, + [13] = &gp_fault, + [14] = &pf_fault, + [16] = &mf_fault, + [17] = &ac_fault, + [18] = &mc_fault, + [19] = &xm_fault, +}; + +void setup_idt(void) +{ + int i; + static bool idt_initialized = false; + + if (idt_initialized) { + return; + } + idt_initialized = true; + for (i = 0; i < 32; i++) + if (idt_handlers[i]) + set_idt_entry(i, idt_handlers[i], 0); + handle_exception(0, check_exception_table); + handle_exception(6, check_exception_table); + handle_exception(13, check_exception_table); +} + +unsigned exception_vector(void) +{ + unsigned short vector; + + asm("mov %%gs:4, %0" : "=rm"(vector)); + return vector; +} + +unsigned exception_error_code(void) +{ + unsigned short error_code; + + asm("mov %%gs:6, %0" : "=rm"(error_code)); + return error_code; +} + +#ifndef __x86_64__ +/* + * GDT, with 6 entries: + * 0x00 - NULL descriptor + * 0x08 - Code segment + * 0x10 - Data segment + * 0x18 - Not presend code segment + * 0x20 - Primery task + * 0x28 - Interrupt task + * + * 0x30 to 0x48 - Free to use for test cases + */ + +static gdt_entry_t gdt[10]; +#define TSS_GDT_OFFSET 4 + +void set_gdt_entry(int num, u32 base, u32 limit, u8 access, u8 gran) +{ + /* Setup the descriptor base address */ + gdt[num].base_low = (base & 0xFFFF); + gdt[num].base_middle = (base >> 16) & 0xFF; + gdt[num].base_high = (base >> 24) & 0xFF; + + /* Setup the descriptor limits */ + gdt[num].limit_low = (limit & 0xFFFF); + gdt[num].granularity = ((limit >> 16) & 0x0F); + + /* Finally, set up the granularity and access flags */ + gdt[num].granularity |= (gran & 0xF0); + gdt[num].access = access; +} + +void setup_gdt(void) +{ + struct descriptor_table_ptr gp; + /* Setup the GDT pointer and limit */ + gp.limit = sizeof(gdt) - 1; + gp.base = (ulong)&gdt; + + memset(gdt, 0, sizeof(gdt)); + + /* Our NULL descriptor */ + set_gdt_entry(0, 0, 0, 0, 0); + + /* The second entry is our Code Segment. The base address + * is 0, the limit is 4GBytes, it uses 4KByte granularity, + * uses 32-bit opcodes, and is a Code Segment descriptor. */ + set_gdt_entry(1, 0, 0xFFFFFFFF, 0x9A, 0xcf); + + /* The third entry is our Data Segment. It's EXACTLY the + * same as our code segment, but the descriptor type in + * this entry's access byte says it's a Data Segment */ + set_gdt_entry(2, 0, 0xFFFFFFFF, 0x92, 0xcf); + + /* Same as code register above but not present */ + set_gdt_entry(3, 0, 0xFFFFFFFF, 0x1A, 0xcf); + + + /* Flush out the old GDT and install the new changes! */ + lgdt(&gp); + + asm volatile ("mov %0, %%ds\n\t" + "mov %0, %%es\n\t" + "mov %0, %%fs\n\t" + "mov %0, %%gs\n\t" + "mov %0, %%ss\n\t" + "jmp $0x08, $.Lflush2\n\t" + ".Lflush2: "::"r"(0x10)); +} + +void set_idt_task_gate(int vec, u16 sel) +{ + idt_entry_t *e = &boot_idt[vec]; + + memset(e, 0, sizeof *e); + + e->selector = sel; + e->ist = 0; + e->type = 5; + e->dpl = 0; + e->p = 1; +} + +/* + * 0 - main task + * 1 - interrupt task + */ + +static tss32_t tss[2]; +static char tss_stack[2][4096]; + +void setup_tss32(void) +{ + u16 desc_size = sizeof(tss32_t); + int i; + + for (i = 0; i < 2; i++) { + tss[i].cr3 = read_cr3(); + tss[i].ss0 = tss[i].ss1 = tss[i].ss2 = 0x10; + tss[i].esp = tss[i].esp0 = tss[i].esp1 = tss[i].esp2 = + (u32)tss_stack[i] + 4096; + tss[i].cs = 0x08; + tss[i].ds = tss[i].es = tss[i].fs = tss[i].gs = tss[i].ss = 0x10; + tss[i].iomap_base = (u16)desc_size; + set_gdt_entry(TSS_GDT_OFFSET + i, (u32)&tss[i], + desc_size - 1, 0x89, 0x0f); + } + + ltr(TSS_MAIN); +} + +void set_intr_task_gate(int e, void *fn) +{ + tss[1].eip = (u32)fn; + set_idt_task_gate(e, TSS_INTR); +} + +void print_current_tss_info(void) +{ + u16 tr = str(); + int i = (tr == TSS_MAIN) ? 0 : 1; + + if (tr != TSS_MAIN && tr != TSS_INTR) + printf("Unknown TSS %x\n", tr); + else + printf("TR=%x Main TSS back link %x. Current TSS back link %x\n", + tr, tss[0].prev, tss[i].prev); +} +#endif diff --git a/kvm-unittest/lib/x86/fwcfg.c b/kvm-unittest/lib/x86/fwcfg.c new file mode 100644 index 0000000..e2cdd15 --- /dev/null +++ b/kvm-unittest/lib/x86/fwcfg.c @@ -0,0 +1,45 @@ +#include "fwcfg.h" +#include "smp.h" + +static struct spinlock lock; + +uint64_t fwcfg_get_u(uint16_t index, int bytes) +{ + uint64_t r = 0; + uint8_t b; + int i; + + spin_lock(&lock); + asm volatile ("out %0, %1" : : "a"(index), "d"((uint16_t)BIOS_CFG_IOPORT)); + for (i = 0; i < bytes; ++i) { + asm volatile ("in %1, %0" : "=a"(b) : "d"((uint16_t)(BIOS_CFG_IOPORT + 1))); + r |= (uint64_t)b << (i * 8); + } + spin_unlock(&lock); + return r; +} + +uint8_t fwcfg_get_u8(unsigned index) +{ + return fwcfg_get_u(index, 1); +} + +uint16_t fwcfg_get_u16(unsigned index) +{ + return fwcfg_get_u(index, 2); +} + +uint32_t fwcfg_get_u32(unsigned index) +{ + return fwcfg_get_u(index, 4); +} + +uint64_t fwcfg_get_u64(unsigned index) +{ + return fwcfg_get_u(index, 8); +} + +unsigned fwcfg_get_nb_cpus(void) +{ + return fwcfg_get_u16(FW_CFG_NB_CPUS); +} diff --git a/kvm-unittest/lib/x86/io.c b/kvm-unittest/lib/x86/io.c new file mode 100644 index 0000000..d3b971e --- /dev/null +++ b/kvm-unittest/lib/x86/io.c @@ -0,0 +1,83 @@ +#include "libcflat.h" +#include "smp.h" +#include "io.h" +#ifndef USE_SERIAL +#define USE_SERIAL +#endif + +static struct spinlock lock; +static int serial_iobase = 0x3f8; +static int serial_inited = 0; + +static void serial_outb(char ch) +{ + u8 lsr; + + do { + lsr = inb(serial_iobase + 0x05); + } while (!(lsr & 0x20)); + + outb(ch, serial_iobase + 0x00); +} + +static void serial_init(void) +{ + u8 lcr; + + /* set DLAB */ + lcr = inb(serial_iobase + 0x03); + lcr |= 0x80; + outb(lcr, serial_iobase + 0x03); + + /* set baud rate to 115200 */ + outb(0x01, serial_iobase + 0x00); + outb(0x00, serial_iobase + 0x01); + + /* clear DLAB */ + lcr = inb(serial_iobase + 0x03); + lcr &= ~0x80; + outb(lcr, serial_iobase + 0x03); +} + +static void print_serial(const char *buf) +{ + unsigned long len = strlen(buf); +#ifdef USE_SERIAL + unsigned long i; + if (!serial_inited) { + serial_init(); + serial_inited = 1; + } + + for (i = 0; i < len; i++) { + serial_outb(buf[i]); + } +#else + asm volatile ("rep/outsb" : "+S"(buf), "+c"(len) : "d"(0xf1)); +#endif +} + +void puts(const char *s) +{ + spin_lock(&lock); + print_serial(s); + spin_unlock(&lock); +} + +void exit(int code) +{ +#ifdef USE_SERIAL + static const char shutdown_str[8] = "Shutdown"; + int i; + + /* test device exit (with status) */ + outl(code, 0xf4); + + /* if that failed, try the Bochs poweroff port */ + for (i = 0; i < 8; i++) { + outb(shutdown_str[i], 0x8900); + } +#else + asm volatile("out %0, %1" : : "a"(code), "d"((short)0xf4)); +#endif +} diff --git a/kvm-unittest/lib/x86/isr.c b/kvm-unittest/lib/x86/isr.c new file mode 100644 index 0000000..9986d17 --- /dev/null +++ b/kvm-unittest/lib/x86/isr.c @@ -0,0 +1,98 @@ +#include "libcflat.h" +#include "isr.h" +#include "vm.h" +#include "desc.h" + +#ifdef __x86_64__ +# define R "r" +#else +# define R "e" +#endif + +extern char isr_entry_point[]; + +asm ( + "isr_entry_point: \n" +#ifdef __x86_64__ + "push %r15 \n\t" + "push %r14 \n\t" + "push %r13 \n\t" + "push %r12 \n\t" + "push %r11 \n\t" + "push %r10 \n\t" + "push %r9 \n\t" + "push %r8 \n\t" +#endif + "push %"R "di \n\t" + "push %"R "si \n\t" + "push %"R "bp \n\t" + "push %"R "sp \n\t" + "push %"R "bx \n\t" + "push %"R "dx \n\t" + "push %"R "cx \n\t" + "push %"R "ax \n\t" +#ifdef __x86_64__ + "mov %rsp, %rdi \n\t" + "callq *8*16(%rsp) \n\t" +#else + "push %esp \n\t" + "calll *4+4*8(%esp) \n\t" + "add $4, %esp \n\t" +#endif + "pop %"R "ax \n\t" + "pop %"R "cx \n\t" + "pop %"R "dx \n\t" + "pop %"R "bx \n\t" + "pop %"R "bp \n\t" + "pop %"R "bp \n\t" + "pop %"R "si \n\t" + "pop %"R "di \n\t" +#ifdef __x86_64__ + "pop %r8 \n\t" + "pop %r9 \n\t" + "pop %r10 \n\t" + "pop %r11 \n\t" + "pop %r12 \n\t" + "pop %r13 \n\t" + "pop %r14 \n\t" + "pop %r15 \n\t" +#endif + ".globl isr_iret_ip\n\t" +#ifdef __x86_64__ + "add $8, %rsp \n\t" + "isr_iret_ip: \n\t" + "iretq \n\t" +#else + "add $4, %esp \n\t" + "isr_iret_ip: \n\t" + "iretl \n\t" +#endif + ); + +void handle_irq(unsigned vec, void (*func)(isr_regs_t *regs)) +{ + u8 *thunk = vmalloc(50); + + set_idt_entry(vec, thunk, 0); + +#ifdef __x86_64__ + /* sub $8, %rsp */ + *thunk++ = 0x48; *thunk++ = 0x83; *thunk++ = 0xec; *thunk++ = 0x08; + /* mov $func_low, %(rsp) */ + *thunk++ = 0xc7; *thunk++ = 0x04; *thunk++ = 0x24; + *(u32 *)thunk = (ulong)func; thunk += 4; + /* mov $func_high, %(rsp+4) */ + *thunk++ = 0xc7; *thunk++ = 0x44; *thunk++ = 0x24; *thunk++ = 0x04; + *(u32 *)thunk = (ulong)func >> 32; thunk += 4; + /* jmp isr_entry_point */ + *thunk ++ = 0xe9; + *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4); +#else + /* push $func */ + *thunk++ = 0x68; + *(u32 *)thunk = (ulong)func; thunk += 4; + /* jmp isr_entry_point */ + *thunk++ = 0xe9; + *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4); +#endif +} diff --git a/kvm-unittest/lib/x86/pci.c b/kvm-unittest/lib/x86/pci.c new file mode 100644 index 0000000..f95cd88 --- /dev/null +++ b/kvm-unittest/lib/x86/pci.c @@ -0,0 +1,55 @@ +#include +#include "pci.h" + +static void outl(unsigned short port, unsigned val) +{ + asm volatile("outl %d0, %w1" : : "a"(val), "Nd"(port)); +} + +static unsigned inl(unsigned short port) +{ + unsigned data; + asm volatile("inl %w1, %d0" : "=a"(data) : "Nd"(port)); + return data; +} +static uint32_t pci_config_read(pcidevaddr_t dev, uint8_t reg) +{ + uint32_t index = reg | (dev << 8) | (0x1 << 31); + outl(0xCF8, index); + return inl(0xCFC); +} + +/* Scan bus look for a specific device. Only bus 0 scanned for now. */ +pcidevaddr_t pci_find_dev(uint16_t vendor_id, uint16_t device_id) +{ + unsigned dev; + for (dev = 0; dev < 256; ++dev) { + uint32_t id = pci_config_read(dev, 0); + if ((id & 0xFFFF) == vendor_id && (id >> 16) == device_id) { + return dev; + } + } + return PCIDEVADDR_INVALID; +} + +unsigned long pci_bar_addr(pcidevaddr_t dev, int bar_num) +{ + uint32_t bar = pci_config_read(dev, PCI_BASE_ADDRESS_0 + bar_num * 4); + if (bar & PCI_BASE_ADDRESS_SPACE_IO) { + return bar & PCI_BASE_ADDRESS_IO_MASK; + } else { + return bar & PCI_BASE_ADDRESS_MEM_MASK; + } +} + +bool pci_bar_is_memory(pcidevaddr_t dev, int bar_num) +{ + uint32_t bar = pci_config_read(dev, PCI_BASE_ADDRESS_0 + bar_num * 4); + return !(bar & PCI_BASE_ADDRESS_SPACE_IO); +} + +bool pci_bar_is_valid(pcidevaddr_t dev, int bar_num) +{ + uint32_t bar = pci_config_read(dev, PCI_BASE_ADDRESS_0 + bar_num * 4); + return bar; +} diff --git a/kvm-unittest/lib/x86/smp.c b/kvm-unittest/lib/x86/smp.c new file mode 100644 index 0000000..d4c8106 --- /dev/null +++ b/kvm-unittest/lib/x86/smp.c @@ -0,0 +1,124 @@ + +#include +#include "smp.h" +#include "apic.h" +#include "fwcfg.h" + +#define IPI_VECTOR 0x20 + +typedef void (*ipi_function_type)(void *data); + +static struct spinlock ipi_lock; +static volatile ipi_function_type ipi_function; +static volatile void *ipi_data; +static volatile int ipi_done; +static volatile bool ipi_wait; +static int _cpu_count; + +static __attribute__((used)) void ipi() +{ + void (*function)(void *data) = ipi_function; + void *data = ipi_data; + bool wait = ipi_wait; + + if (!wait) { + ipi_done = 1; + apic_write(APIC_EOI, 0); + } + function(data); + if (wait) { + ipi_done = 1; + apic_write(APIC_EOI, 0); + } +} + +asm ( + "ipi_entry: \n" + " call ipi \n" +#ifndef __x86_64__ + " iret" +#else + " iretq" +#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; +} + +int smp_id(void) +{ + unsigned id; + + asm ("mov %%gs:0, %0" : "=r"(id)); + return id; +} + +static void setup_smp_id(void *data) +{ + asm ("mov %0, %%gs:0" : : "r"(apic_id()) : "memory"); +} + +static void __on_cpu(int cpu, void (*function)(void *data), void *data, + int wait) +{ + spin_lock(&ipi_lock); + if (cpu == smp_id()) + function(data); + else { + ipi_done = 0; + ipi_function = function; + ipi_data = data; + ipi_wait = wait; + apic_icr_write(APIC_INT_ASSERT | APIC_DEST_PHYSICAL | APIC_DM_FIXED + | IPI_VECTOR, + cpu); + while (!ipi_done) + ; + } + spin_unlock(&ipi_lock); +} + +void on_cpu(int cpu, void (*function)(void *data), void *data) +{ + __on_cpu(cpu, function, data, 1); +} + +void on_cpu_async(int cpu, void (*function)(void *data), void *data) +{ + __on_cpu(cpu, function, data, 0); +} + + +void smp_init(void) +{ + int i; + void ipi_entry(void); + + _cpu_count = fwcfg_get_nb_cpus(); + + setup_idt(); + set_idt_entry(IPI_VECTOR, ipi_entry, 0); + + setup_smp_id(0); + for (i = 1; i < cpu_count(); ++i) + on_cpu(i, setup_smp_id, 0); + +} diff --git a/kvm-unittest/lib/x86/vm.c b/kvm-unittest/lib/x86/vm.c new file mode 100644 index 0000000..188bf57 --- /dev/null +++ b/kvm-unittest/lib/x86/vm.c @@ -0,0 +1,256 @@ +#include "fwcfg.h" +#include "vm.h" +#include "libcflat.h" + +#define PAGE_SIZE 4096ul +#ifdef __x86_64__ +#define LARGE_PAGE_SIZE (512 * PAGE_SIZE) +#else +#define LARGE_PAGE_SIZE (1024 * PAGE_SIZE) +#endif + +static void *free = 0; +static void *vfree_top = 0; + +static void free_memory(void *mem, unsigned long size) +{ + while (size >= PAGE_SIZE) { + *(void **)mem = free; + free = mem; + mem += PAGE_SIZE; + size -= PAGE_SIZE; + } +} + +void *alloc_page() +{ + void *p; + + if (!free) + return 0; + + p = free; + free = *(void **)free; + + return p; +} + +void free_page(void *page) +{ + *(void **)page = free; + free = page; +} + +extern char edata; +static unsigned long end_of_memory; + +#ifdef __x86_64__ +#define PAGE_LEVEL 4 +#define PGDIR_WIDTH 9 +#define PGDIR_MASK 511 +#else +#define PAGE_LEVEL 2 +#define PGDIR_WIDTH 10 +#define PGDIR_MASK 1023 +#endif + +void install_pte(unsigned long *cr3, + int pte_level, + void *virt, + unsigned long pte, + unsigned long *pt_page) +{ + int level; + unsigned long *pt = cr3; + unsigned offset; + + for (level = PAGE_LEVEL; level > pte_level; --level) { + offset = ((unsigned long)virt >> ((level-1) * PGDIR_WIDTH + 12)) & PGDIR_MASK; + if (!(pt[offset] & PTE_PRESENT)) { + unsigned long *new_pt = pt_page; + if (!new_pt) + new_pt = alloc_page(); + else + pt_page = 0; + memset(new_pt, 0, PAGE_SIZE); + pt[offset] = virt_to_phys(new_pt) | PTE_PRESENT | PTE_WRITE; + } + pt = phys_to_virt(pt[offset] & 0xffffffffff000ull); + } + offset = ((unsigned long)virt >> ((level-1) * PGDIR_WIDTH + 12)) & PGDIR_MASK; + pt[offset] = pte; +} + +static unsigned long get_pte(unsigned long *cr3, void *virt) +{ + int level; + unsigned long *pt = cr3, pte; + unsigned offset; + + for (level = PAGE_LEVEL; level > 1; --level) { + offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK; + pte = pt[offset]; + if (!(pte & PTE_PRESENT)) + return 0; + if (level == 2 && (pte & PTE_PSE)) + return pte; + pt = phys_to_virt(pte & 0xffffffffff000ull); + } + offset = ((unsigned long)virt >> (((level-1) * PGDIR_WIDTH) + 12)) & PGDIR_MASK; + pte = pt[offset]; + return pte; +} + +void install_large_page(unsigned long *cr3, + unsigned long phys, + void *virt) +{ + install_pte(cr3, 2, virt, phys | PTE_PRESENT | PTE_WRITE | PTE_USER | PTE_PSE, 0); +} + +void install_page(unsigned long *cr3, + unsigned long phys, + void *virt) +{ + install_pte(cr3, 1, virt, phys | PTE_PRESENT | PTE_WRITE | PTE_USER, 0); +} + + +static inline void load_gdt(unsigned long *table, int nent) +{ + struct descriptor_table_ptr descr; + + descr.limit = nent * 8 - 1; + descr.base = (ulong)table; + lgdt(&descr); +} + +#define SEG_CS_32 8 +#define SEG_CS_64 16 + +struct ljmp { + void *ofs; + unsigned short seg; +}; + +static void setup_mmu_range(unsigned long *cr3, unsigned long start, + unsigned long len) +{ + u64 max = (u64)len + (u64)start; + u64 phys = start; + + while (phys + LARGE_PAGE_SIZE <= max) { + 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; + } +} + +static void setup_mmu(unsigned long len) +{ + unsigned long *cr3 = alloc_page(); + + memset(cr3, 0, PAGE_SIZE); + +#ifdef __x86_64__ + if (len < (1ul << 32)) + len = (1ul << 32); /* map mmio 1:1 */ + + setup_mmu_range(cr3, 0, len); +#else + if (len > (1ul << 31)) + len = (1ul << 31); + + /* 0 - 2G memory, 2G-3G valloc area, 3G-4G mmio */ + setup_mmu_range(cr3, 0, len); + setup_mmu_range(cr3, 3ul << 30, (1ul << 30)); + vfree_top = (void*)(3ul << 30); +#endif + + write_cr3(virt_to_phys(cr3)); +#ifndef __x86_64__ + write_cr4(X86_CR4_PSE); +#endif + write_cr0(X86_CR0_PG |X86_CR0_PE | X86_CR0_WP); + + printf("paging enabled\n"); + printf("cr0 = %x\n", read_cr0()); + printf("cr3 = %x\n", read_cr3()); + printf("cr4 = %x\n", read_cr4()); +} + +void setup_vm() +{ + end_of_memory = fwcfg_get_u64(FW_CFG_RAM_SIZE); + free_memory(&edata, end_of_memory - (unsigned long)&edata); + setup_mmu(end_of_memory); +} + +void *vmalloc(unsigned long size) +{ + void *mem, *p; + unsigned pages; + + size += sizeof(unsigned long); + + size = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + vfree_top -= size; + mem = p = vfree_top; + pages = size / PAGE_SIZE; + while (pages--) { + install_page(phys_to_virt(read_cr3()), virt_to_phys(alloc_page()), p); + p += PAGE_SIZE; + } + *(unsigned long *)mem = size; + mem += sizeof(unsigned long); + return mem; +} + +uint64_t virt_to_phys_cr3(void *mem) +{ + return (get_pte(phys_to_virt(read_cr3()), mem) & PTE_ADDR) + ((ulong)mem & (PAGE_SIZE - 1)); +} + +void vfree(void *mem) +{ + unsigned long size = ((unsigned long *)mem)[-1]; + + while (size) { + free_page(phys_to_virt(get_pte(phys_to_virt(read_cr3()), mem) & PTE_ADDR)); + mem += PAGE_SIZE; + size -= PAGE_SIZE; + } +} + +void *vmap(unsigned long long phys, unsigned long size) +{ + void *mem, *p; + unsigned pages; + + size = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); + vfree_top -= size; + phys &= ~(unsigned long long)(PAGE_SIZE - 1); + + mem = p = vfree_top; + pages = size / PAGE_SIZE; + while (pages--) { + install_page(phys_to_virt(read_cr3()), phys, p); + phys += PAGE_SIZE; + p += PAGE_SIZE; + } + return mem; +} + +void *alloc_vpages(ulong nr) +{ + vfree_top -= PAGE_SIZE * nr; + return vfree_top; +} + +void *alloc_vpage(void) +{ + return alloc_vpages(1); +} diff --git a/kvm-unittest/main-ppc.c b/kvm-unittest/main-ppc.c new file mode 100644 index 0000000..5af59f8 --- /dev/null +++ b/kvm-unittest/main-ppc.c @@ -0,0 +1,383 @@ +/* + * Kernel-based Virtual Machine test driver + * + * This test driver provides a simple way of testing kvm, without a full + * device model. + * + * Copyright (C) 2006 Qumranet + * Copyright IBM Corp. 2008 + * + * Authors: + * + * Avi Kivity + * Yaniv Kamay + * Hollis Blanchard + * + * This work is licensed under the GNU LGPL license, version 2. + */ + +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iotable.h" + +static int gettid(void) +{ + return syscall(__NR_gettid); +} + +kvm_context_t kvm; + +#define IPI_SIGNAL (SIGRTMIN + 4) + +struct io_table mmio_table; + +static int ncpus = 1; +static sem_t exited_sem; +static __thread int vcpu; +static sigset_t kernel_sigmask; +static sigset_t ipi_sigmask; +static uint64_t memory_size = 128 * 1024 * 1024; + +struct vcpu_info { + pid_t tid; +}; + +struct vcpu_info *vcpus; + +/* Must match flat.lds linker script */ +#define VM_TEST_LOAD_ADDRESS 0x100000 + +static int test_debug(void *opaque, void *vcpu) +{ + printf("test_debug\n"); + return 0; +} + +static int test_halt(void *opaque, int vcpu) +{ + int n; + + sigwait(&ipi_sigmask, &n); + return 0; +} + +static int test_io_window(void *opaque) +{ + return 0; +} + +static int test_try_push_interrupts(void *opaque) +{ + return 0; +} + +static void test_post_kvm_run(void *opaque, void *vcpu) +{ +} + +static int test_pre_kvm_run(void *opaque, void *vcpu) +{ + return 0; +} + +static int mmio_handler(void *opaque, int len, int is_write, uint64_t offset, + uint64_t *data) +{ + int r = 0; + + switch (offset) { + case 0: /* putc */ + putc(*(char *)data, stdout); + fflush(stdout); + break; + case 1: /* exit */ + r = *(char *)data; + break; + default: + printf("%s: offset %"PRIx64" len %d data %"PRIx64"\n", + __func__, offset, len, *(uint64_t *)data); + r = -EINVAL; + } + + return r; +} + +static int test_mem_read(void *opaque, uint64_t addr, uint8_t *data, int len) +{ + struct io_table_entry *iodev; + +#if 0 + printf("%s: addr %"PRIx64" len %d\n", __func__, addr, len); +#endif + + iodev = io_table_lookup(&mmio_table, addr); + if (!iodev) { + printf("couldn't find device\n"); + return -ENODEV; + } + + return iodev->handler(iodev->opaque, len, 0, addr - iodev->start, + (uint64_t *)data); +} + +static int test_mem_write(void *opaque, uint64_t addr, uint8_t *data, int len) +{ + struct io_table_entry *iodev; + +#if 0 + printf("%s: addr %"PRIx64" len %d data %"PRIx64"\n", + __func__, addr, len, *(uint64_t *)data); +#endif + + iodev = io_table_lookup(&mmio_table, addr); + if (!iodev) { + printf("couldn't find device\n"); + return -ENODEV; + } + + return iodev->handler(iodev->opaque, len, 1, addr - iodev->start, + (uint64_t *)data); +} + +static int test_dcr_read(int vcpu, uint32_t dcrn, uint32_t *data) +{ + printf("%s: dcrn %04X\n", __func__, dcrn); + *data = 0; + return 0; +} + +static int test_dcr_write(int vcpu, uint32_t dcrn, uint32_t data) +{ + printf("%s: dcrn %04X data %04X\n", __func__, dcrn, data); + return 0; +} + +static struct kvm_callbacks test_callbacks = { + .mmio_read = test_mem_read, + .mmio_write = test_mem_write, + .debug = test_debug, + .halt = test_halt, + .io_window = test_io_window, + .try_push_interrupts = test_try_push_interrupts, + .post_kvm_run = test_post_kvm_run, + .pre_kvm_run = test_pre_kvm_run, + .powerpc_dcr_read = test_dcr_read, + .powerpc_dcr_write = test_dcr_write, +}; + +static unsigned long load_file(void *mem, const char *fname, int inval_icache) +{ + ssize_t r; + int fd; + unsigned long bytes = 0; + + fd = open(fname, O_RDONLY); + if (fd == -1) { + perror("open"); + exit(1); + } + + while ((r = read(fd, mem, 4096)) != -1 && r != 0) { + mem += r; + bytes += r; + } + + if (r == -1) { + perror("read"); + printf("read %d bytes\n", bytes); + exit(1); + } + + return bytes; +} + +#define ICACHE_LINE_SIZE 32 + +void sync_caches(void *mem, unsigned long len) +{ + unsigned long i; + + for (i = 0; i < len; i += ICACHE_LINE_SIZE) + asm volatile ("dcbst %0, %1" : : "g"(mem), "r"(i)); + asm volatile ("sync"); + for (i = 0; i < len; i += ICACHE_LINE_SIZE) + asm volatile ("icbi %0, %1" : : "g"(mem), "r"(i)); + asm volatile ("sync; isync"); +} + +static void init_vcpu(int n) +{ + sigemptyset(&ipi_sigmask); + sigaddset(&ipi_sigmask, IPI_SIGNAL); + sigprocmask(SIG_UNBLOCK, &ipi_sigmask, NULL); + sigprocmask(SIG_BLOCK, &ipi_sigmask, &kernel_sigmask); + vcpus[n].tid = gettid(); + vcpu = n; + kvm_set_signal_mask(kvm, n, &kernel_sigmask); +} + +static void *do_create_vcpu(void *_n) +{ + struct kvm_regs regs; + int n = (long)_n; + + kvm_create_vcpu(kvm, n); + init_vcpu(n); + + kvm_get_regs(kvm, n, ®s); + regs.pc = VM_TEST_LOAD_ADDRESS; + kvm_set_regs(kvm, n, ®s); + + kvm_run(kvm, n, &vcpus[n]); + sem_post(&exited_sem); + return NULL; +} + +static void start_vcpu(int n) +{ + pthread_t thread; + + pthread_create(&thread, NULL, do_create_vcpu, (void *)(long)n); +} + +static void usage(const char *progname) +{ + fprintf(stderr, +"Usage: %s [OPTIONS] [bootstrap] flatfile\n" +"KVM test harness.\n" +"\n" +" -s, --smp=NUM create a VM with NUM virtual CPUs\n" +" -m, --memory=NUM[GMKB] allocate NUM memory for virtual machine. A suffix\n" +" can be used to change the unit (default: `M')\n" +" -h, --help display this help screen and exit\n" +"\n" +"Report bugs to .\n" + , progname); +} + +static void sig_ignore(int sig) +{ + write(1, "boo\n", 4); +} + +int main(int argc, char **argv) +{ + void *vm_mem; + unsigned long len; + int i; + const char *sopts = "s:phm:"; + struct option lopts[] = { + { "smp", 1, 0, 's' }, + { "memory", 1, 0, 'm' }, + { "help", 0, 0, 'h' }, + { 0 }, + }; + int opt_ind, ch; + int nb_args; + char *endptr; + + while ((ch = getopt_long(argc, argv, sopts, lopts, &opt_ind)) != -1) { + switch (ch) { + case 's': + ncpus = atoi(optarg); + break; + case 'm': + memory_size = strtoull(optarg, &endptr, 0); + switch (*endptr) { + case 'G': case 'g': + memory_size <<= 30; + break; + case '\0': + case 'M': case 'm': + memory_size <<= 20; + break; + case 'K': case 'k': + memory_size <<= 10; + break; + default: + fprintf(stderr, + "Unrecongized memory suffix: %c\n", + *endptr); + exit(1); + } + if (memory_size == 0) { + fprintf(stderr, + "Invalid memory size: 0\n"); + exit(1); + } + break; + case 'h': + usage(argv[0]); + exit(0); + case '?': + default: + fprintf(stderr, + "Try `%s --help' for more information.\n", + argv[0]); + exit(1); + } + } + + nb_args = argc - optind; + if (nb_args < 1 || nb_args > 2) { + fprintf(stderr, + "Incorrect number of arguments.\n" + "Try `%s --help' for more information.\n", + argv[0]); + exit(1); + } + + signal(IPI_SIGNAL, sig_ignore); + + vcpus = calloc(ncpus, sizeof *vcpus); + if (!vcpus) { + fprintf(stderr, "calloc failed\n"); + return 1; + } + + kvm = kvm_init(&test_callbacks, 0); + if (!kvm) { + fprintf(stderr, "kvm_init failed\n"); + return 1; + } + if (kvm_create(kvm, memory_size, &vm_mem) < 0) { + kvm_finalize(kvm); + fprintf(stderr, "kvm_create failed\n"); + return 1; + } + + vm_mem = kvm_create_phys_mem(kvm, 0, memory_size, 0, 1); + + len = load_file(vm_mem + VM_TEST_LOAD_ADDRESS, argv[optind], 1); + sync_caches(vm_mem + VM_TEST_LOAD_ADDRESS, len); + + io_table_register(&mmio_table, 0xf0000000, 64, mmio_handler, NULL); + + sem_init(&exited_sem, 0, 0); + for (i = 0; i < ncpus; ++i) + start_vcpu(i); + /* Wait for all vcpus to exit. */ + for (i = 0; i < ncpus; ++i) + sem_wait(&exited_sem); + + return 0; +} diff --git a/kvm-unittest/powerpc/exit.c b/kvm-unittest/powerpc/exit.c new file mode 100644 index 0000000..804ee04 --- /dev/null +++ b/kvm-unittest/powerpc/exit.c @@ -0,0 +1,23 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +int main(void) +{ + return 1; +} diff --git a/kvm-unittest/powerpc/helloworld.c b/kvm-unittest/powerpc/helloworld.c new file mode 100644 index 0000000..f8630f7 --- /dev/null +++ b/kvm-unittest/powerpc/helloworld.c @@ -0,0 +1,27 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Deepa Srinivasan + */ + +#include "libcflat.h" + +int main() +{ + printf("Hello World\n"); + + return 1; +} diff --git a/kvm-unittest/x86/access.c b/kvm-unittest/x86/access.c new file mode 100644 index 0000000..2ca325a --- /dev/null +++ b/kvm-unittest/x86/access.c @@ -0,0 +1,860 @@ + +#include "libcflat.h" +#include "desc.h" +#include "processor.h" + +#define smp_id() 0 + +#define true 1 +#define false 0 + +static _Bool verbose = false; + +typedef unsigned long pt_element_t; + +#define PAGE_SIZE ((pt_element_t)4096) +#define PAGE_MASK (~(PAGE_SIZE-1)) + +#define PT_BASE_ADDR_MASK ((pt_element_t)((((pt_element_t)1 << 40) - 1) & PAGE_MASK)) +#define PT_PSE_BASE_ADDR_MASK (PT_BASE_ADDR_MASK & ~(1ull << 21)) + +#define PT_PRESENT_MASK ((pt_element_t)1 << 0) +#define PT_WRITABLE_MASK ((pt_element_t)1 << 1) +#define PT_USER_MASK ((pt_element_t)1 << 2) +#define PT_ACCESSED_MASK ((pt_element_t)1 << 5) +#define PT_DIRTY_MASK ((pt_element_t)1 << 6) +#define PT_PSE_MASK ((pt_element_t)1 << 7) +#define PT_NX_MASK ((pt_element_t)1 << 63) + +#define CR0_WP_MASK (1UL << 16) +#define CR4_SMEP_MASK (1UL << 20) + +#define PFERR_PRESENT_MASK (1U << 0) +#define PFERR_WRITE_MASK (1U << 1) +#define PFERR_USER_MASK (1U << 2) +#define PFERR_RESERVED_MASK (1U << 3) +#define PFERR_FETCH_MASK (1U << 4) + +#define MSR_EFER 0xc0000080 +#define EFER_NX_MASK (1ull << 11) + +#define PT_INDEX(address, level) \ + ((address) >> (12 + ((level)-1) * 9)) & 511 + +/* + * page table access check tests + */ + +enum { + AC_PTE_PRESENT, + AC_PTE_WRITABLE, + AC_PTE_USER, + AC_PTE_ACCESSED, + AC_PTE_DIRTY, + AC_PTE_NX, + AC_PTE_BIT51, + + AC_PDE_PRESENT, + AC_PDE_WRITABLE, + AC_PDE_USER, + AC_PDE_ACCESSED, + AC_PDE_DIRTY, + AC_PDE_PSE, + AC_PDE_NX, + AC_PDE_BIT51, + + AC_ACCESS_USER, + AC_ACCESS_WRITE, + AC_ACCESS_FETCH, + AC_ACCESS_TWICE, + // AC_ACCESS_PTE, + + AC_CPU_EFER_NX, + AC_CPU_CR0_WP, + AC_CPU_CR4_SMEP, + + NR_AC_FLAGS +}; + +const char *ac_names[] = { + [AC_PTE_PRESENT] = "pte.p", + [AC_PTE_ACCESSED] = "pte.a", + [AC_PTE_WRITABLE] = "pte.rw", + [AC_PTE_USER] = "pte.user", + [AC_PTE_DIRTY] = "pte.d", + [AC_PTE_NX] = "pte.nx", + [AC_PTE_BIT51] = "pte.51", + [AC_PDE_PRESENT] = "pde.p", + [AC_PDE_ACCESSED] = "pde.a", + [AC_PDE_WRITABLE] = "pde.rw", + [AC_PDE_USER] = "pde.user", + [AC_PDE_DIRTY] = "pde.d", + [AC_PDE_PSE] = "pde.pse", + [AC_PDE_NX] = "pde.nx", + [AC_PDE_BIT51] = "pde.51", + [AC_ACCESS_WRITE] = "write", + [AC_ACCESS_USER] = "user", + [AC_ACCESS_FETCH] = "fetch", + [AC_ACCESS_TWICE] = "twice", + [AC_CPU_EFER_NX] = "efer.nx", + [AC_CPU_CR0_WP] = "cr0.wp", + [AC_CPU_CR4_SMEP] = "cr4.smep", +}; + +static inline void *va(pt_element_t phys) +{ + return (void *)phys; +} + +typedef struct { + pt_element_t pt_pool; + unsigned pt_pool_size; + unsigned pt_pool_current; +} ac_pool_t; + +typedef struct { + unsigned flags[NR_AC_FLAGS]; + void *virt; + pt_element_t phys; + pt_element_t *ptep; + pt_element_t expected_pte; + pt_element_t *pdep; + pt_element_t expected_pde; + pt_element_t ignore_pde; + int expected_fault; + unsigned expected_error; +} ac_test_t; + +typedef struct { + unsigned short limit; + unsigned long linear_addr; +} __attribute__((packed)) descriptor_table_t; + + +static void ac_test_show(ac_test_t *at); + +int write_cr4_checking(unsigned long val) +{ + asm volatile(ASM_TRY("1f") + "mov %0,%%cr4\n\t" + "1:": : "r" (val)); + return exception_vector(); +} + +void set_cr0_wp(int wp) +{ + unsigned long cr0 = read_cr0(); + + cr0 &= ~CR0_WP_MASK; + if (wp) + cr0 |= CR0_WP_MASK; + write_cr0(cr0); +} + +void set_cr4_smep(int smep) +{ + unsigned long cr4 = read_cr4(); + + cr4 &= ~CR4_SMEP_MASK; + if (smep) + cr4 |= CR4_SMEP_MASK; + write_cr4(cr4); +} + +void set_efer_nx(int nx) +{ + unsigned long long efer; + + efer = rdmsr(MSR_EFER); + efer &= ~EFER_NX_MASK; + if (nx) + efer |= EFER_NX_MASK; + wrmsr(MSR_EFER, efer); +} + +static void ac_env_int(ac_pool_t *pool) +{ + setup_idt(); + + extern char page_fault, kernel_entry; + set_idt_entry(14, &page_fault, 0); + set_idt_entry(0x20, &kernel_entry, 3); + + pool->pt_pool = 33 * 1024 * 1024; + pool->pt_pool_size = 120 * 1024 * 1024 - pool->pt_pool; + pool->pt_pool_current = 0; +} + +void ac_test_init(ac_test_t *at, void *virt) +{ + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_NX_MASK); + set_cr0_wp(1); + for (int i = 0; i < NR_AC_FLAGS; ++i) + at->flags[i] = 0; + at->virt = virt; + at->phys = 32 * 1024 * 1024; +} + +int ac_test_bump_one(ac_test_t *at) +{ + for (int i = 0; i < NR_AC_FLAGS; ++i) + if (!at->flags[i]) { + at->flags[i] = 1; + return 1; + } else + at->flags[i] = 0; + return 0; +} + +_Bool ac_test_legal(ac_test_t *at) +{ + /* + * Since we convert current page to kernel page when cr4.smep=1, + * we can't switch to user mode. + */ + if ((at->flags[AC_ACCESS_FETCH] && at->flags[AC_ACCESS_WRITE]) || + (at->flags[AC_ACCESS_USER] && at->flags[AC_CPU_CR4_SMEP])) + return false; + return true; +} + +int ac_test_bump(ac_test_t *at) +{ + int ret; + + ret = ac_test_bump_one(at); + while (ret && !ac_test_legal(at)) + ret = ac_test_bump_one(at); + return ret; +} + +pt_element_t ac_test_alloc_pt(ac_pool_t *pool) +{ + pt_element_t ret = pool->pt_pool + pool->pt_pool_current; + pool->pt_pool_current += PAGE_SIZE; + return ret; +} + +_Bool ac_test_enough_room(ac_pool_t *pool) +{ + return pool->pt_pool_current + 4 * PAGE_SIZE <= pool->pt_pool_size; +} + +void ac_test_reset_pt_pool(ac_pool_t *pool) +{ + pool->pt_pool_current = 0; +} + +void ac_set_expected_status(ac_test_t *at) +{ + int pde_valid, pte_valid; + + invlpg(at->virt); + + if (at->ptep) + at->expected_pte = *at->ptep; + at->expected_pde = *at->pdep; + at->ignore_pde = 0; + at->expected_fault = 0; + at->expected_error = PFERR_PRESENT_MASK; + + pde_valid = at->flags[AC_PDE_PRESENT] + && !at->flags[AC_PDE_BIT51] + && !(at->flags[AC_PDE_NX] && !at->flags[AC_CPU_EFER_NX]); + pte_valid = pde_valid + && at->flags[AC_PTE_PRESENT] + && !at->flags[AC_PTE_BIT51] + && !(at->flags[AC_PTE_NX] && !at->flags[AC_CPU_EFER_NX]); + if (at->flags[AC_ACCESS_TWICE]) { + if (pde_valid) { + at->expected_pde |= PT_ACCESSED_MASK; + if (pte_valid) + at->expected_pte |= PT_ACCESSED_MASK; + } + } + + if (at->flags[AC_ACCESS_USER]) + at->expected_error |= PFERR_USER_MASK; + + if (at->flags[AC_ACCESS_WRITE]) + at->expected_error |= PFERR_WRITE_MASK; + + if (at->flags[AC_ACCESS_FETCH]) + at->expected_error |= PFERR_FETCH_MASK; + + if (!at->flags[AC_PDE_PRESENT]) { + at->expected_fault = 1; + at->expected_error &= ~PFERR_PRESENT_MASK; + } else if (!pde_valid) { + at->expected_fault = 1; + at->expected_error |= PFERR_RESERVED_MASK; + } + + if (at->flags[AC_ACCESS_USER] && !at->flags[AC_PDE_USER]) + at->expected_fault = 1; + + if (at->flags[AC_ACCESS_WRITE] + && !at->flags[AC_PDE_WRITABLE] + && (at->flags[AC_CPU_CR0_WP] || at->flags[AC_ACCESS_USER])) + at->expected_fault = 1; + + if (at->flags[AC_ACCESS_FETCH] && at->flags[AC_PDE_NX]) + at->expected_fault = 1; + + if (!at->flags[AC_PDE_ACCESSED]) + at->ignore_pde = PT_ACCESSED_MASK; + + if (!pde_valid) + goto fault; + + if (!at->expected_fault) + at->expected_pde |= PT_ACCESSED_MASK; + + if (at->flags[AC_PDE_PSE]) { + if (at->flags[AC_ACCESS_WRITE] && !at->expected_fault) + at->expected_pde |= PT_DIRTY_MASK; + if (at->flags[AC_ACCESS_FETCH] && at->flags[AC_PDE_USER] + && at->flags[AC_CPU_CR4_SMEP]) + at->expected_fault = 1; + goto no_pte; + } + + if (!at->flags[AC_PTE_PRESENT]) { + at->expected_fault = 1; + at->expected_error &= ~PFERR_PRESENT_MASK; + } else if (!pte_valid) { + at->expected_fault = 1; + at->expected_error |= PFERR_RESERVED_MASK; + } + + if (at->flags[AC_ACCESS_USER] && !at->flags[AC_PTE_USER]) + at->expected_fault = 1; + + if (at->flags[AC_ACCESS_WRITE] + && !at->flags[AC_PTE_WRITABLE] + && (at->flags[AC_CPU_CR0_WP] || at->flags[AC_ACCESS_USER])) + at->expected_fault = 1; + + if (at->flags[AC_ACCESS_FETCH] + && (at->flags[AC_PTE_NX] + || (at->flags[AC_CPU_CR4_SMEP] + && at->flags[AC_PDE_USER] + && at->flags[AC_PTE_USER]))) + at->expected_fault = 1; + + if (at->expected_fault) + goto fault; + + at->expected_pte |= PT_ACCESSED_MASK; + if (at->flags[AC_ACCESS_WRITE]) + at->expected_pte |= PT_DIRTY_MASK; + +no_pte: +fault: + if (!at->expected_fault) + at->ignore_pde = 0; + if (!at->flags[AC_CPU_EFER_NX] && !at->flags[AC_CPU_CR4_SMEP]) + at->expected_error &= ~PFERR_FETCH_MASK; +} + +void __ac_setup_specific_pages(ac_test_t *at, ac_pool_t *pool, u64 pd_page, + u64 pt_page) + +{ + unsigned long root = read_cr3(); + + if (!ac_test_enough_room(pool)) + ac_test_reset_pt_pool(pool); + + at->ptep = 0; + for (int i = 4; i >= 1 && (i >= 2 || !at->flags[AC_PDE_PSE]); --i) { + pt_element_t *vroot = va(root & PT_BASE_ADDR_MASK); + unsigned index = PT_INDEX((unsigned long)at->virt, i); + pt_element_t pte = 0; + switch (i) { + case 4: + case 3: + pte = pd_page ? pd_page : ac_test_alloc_pt(pool); + pte |= PT_PRESENT_MASK | PT_WRITABLE_MASK | PT_USER_MASK; + break; + case 2: + if (!at->flags[AC_PDE_PSE]) + pte = pt_page ? pt_page : ac_test_alloc_pt(pool); + else { + pte = at->phys & PT_PSE_BASE_ADDR_MASK; + pte |= PT_PSE_MASK; + } + if (at->flags[AC_PDE_PRESENT]) + pte |= PT_PRESENT_MASK; + if (at->flags[AC_PDE_WRITABLE]) + pte |= PT_WRITABLE_MASK; + if (at->flags[AC_PDE_USER]) + pte |= PT_USER_MASK; + if (at->flags[AC_PDE_ACCESSED]) + pte |= PT_ACCESSED_MASK; + if (at->flags[AC_PDE_DIRTY]) + pte |= PT_DIRTY_MASK; + if (at->flags[AC_PDE_NX]) + pte |= PT_NX_MASK; + if (at->flags[AC_PDE_BIT51]) + pte |= 1ull << 51; + at->pdep = &vroot[index]; + break; + case 1: + pte = at->phys & PT_BASE_ADDR_MASK; + if (at->flags[AC_PTE_PRESENT]) + pte |= PT_PRESENT_MASK; + if (at->flags[AC_PTE_WRITABLE]) + pte |= PT_WRITABLE_MASK; + if (at->flags[AC_PTE_USER]) + pte |= PT_USER_MASK; + if (at->flags[AC_PTE_ACCESSED]) + pte |= PT_ACCESSED_MASK; + if (at->flags[AC_PTE_DIRTY]) + pte |= PT_DIRTY_MASK; + if (at->flags[AC_PTE_NX]) + pte |= PT_NX_MASK; + if (at->flags[AC_PTE_BIT51]) + pte |= 1ull << 51; + at->ptep = &vroot[index]; + break; + } + vroot[index] = pte; + root = vroot[index]; + } + ac_set_expected_status(at); +} + +static void ac_test_setup_pte(ac_test_t *at, ac_pool_t *pool) +{ + __ac_setup_specific_pages(at, pool, 0, 0); +} + +static void ac_setup_specific_pages(ac_test_t *at, ac_pool_t *pool, + u64 pd_page, u64 pt_page) +{ + return __ac_setup_specific_pages(at, pool, pd_page, pt_page); +} + +static void dump_mapping(ac_test_t *at) +{ + unsigned long root = read_cr3(); + int i; + + printf("Dump mapping: address: %llx\n", at->virt); + for (i = 4; i >= 1 && (i >= 2 || !at->flags[AC_PDE_PSE]); --i) { + pt_element_t *vroot = va(root & PT_BASE_ADDR_MASK); + unsigned index = PT_INDEX((unsigned long)at->virt, i); + pt_element_t pte = vroot[index]; + + printf("------L%d: %llx\n", i, pte); + root = vroot[index]; + } +} + +static void ac_test_check(ac_test_t *at, _Bool *success_ret, _Bool cond, + const char *fmt, ...) +{ + va_list ap; + char buf[500]; + + if (!*success_ret) { + return; + } + + if (!cond) { + return; + } + + *success_ret = false; + + if (!verbose) { + ac_test_show(at); + } + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + printf("FAIL: %s\n", buf); + dump_mapping(at); +} + +static int pt_match(pt_element_t pte1, pt_element_t pte2, pt_element_t ignore) +{ + pte1 &= ~ignore; + pte2 &= ~ignore; + return pte1 == pte2; +} + +int ac_test_do_access(ac_test_t *at) +{ + static unsigned unique = 42; + int fault = 0; + unsigned e; + static unsigned char user_stack[4096]; + unsigned long rsp; + _Bool success = true; + + ++unique; + + *((unsigned char *)at->phys) = 0xc3; /* ret */ + + unsigned r = unique; + set_cr0_wp(at->flags[AC_CPU_CR0_WP]); + set_efer_nx(at->flags[AC_CPU_EFER_NX]); + if (at->flags[AC_CPU_CR4_SMEP] && !(cpuid(7).b & (1 << 7))) { + unsigned long cr4 = read_cr4(); + if (write_cr4_checking(cr4 | CR4_SMEP_MASK) == GP_VECTOR) + goto done; + printf("Set SMEP in CR4 - expect #GP: FAIL!\n"); + return 0; + } + set_cr4_smep(at->flags[AC_CPU_CR4_SMEP]); + + if (at->flags[AC_ACCESS_TWICE]) { + asm volatile ( + "mov $fixed2, %%rsi \n\t" + "mov (%[addr]), %[reg] \n\t" + "fixed2:" + : [reg]"=r"(r), [fault]"=a"(fault), "=b"(e) + : [addr]"r"(at->virt) + : "rsi" + ); + fault = 0; + } + + asm volatile ("mov $fixed1, %%rsi \n\t" + "mov %%rsp, %%rdx \n\t" + "cmp $0, %[user] \n\t" + "jz do_access \n\t" + "push %%rax; mov %[user_ds], %%ax; mov %%ax, %%ds; pop %%rax \n\t" + "pushq %[user_ds] \n\t" + "pushq %[user_stack_top] \n\t" + "pushfq \n\t" + "pushq %[user_cs] \n\t" + "pushq $do_access \n\t" + "iretq \n" + "do_access: \n\t" + "cmp $0, %[fetch] \n\t" + "jnz 2f \n\t" + "cmp $0, %[write] \n\t" + "jnz 1f \n\t" + "mov (%[addr]), %[reg] \n\t" + "jmp done \n\t" + "1: mov %[reg], (%[addr]) \n\t" + "jmp done \n\t" + "2: call *%[addr] \n\t" + "done: \n" + "fixed1: \n" + "int %[kernel_entry_vector] \n\t" + "back_to_kernel:" + : [reg]"+r"(r), "+a"(fault), "=b"(e), "=&d"(rsp) + : [addr]"r"(at->virt), + [write]"r"(at->flags[AC_ACCESS_WRITE]), + [user]"r"(at->flags[AC_ACCESS_USER]), + [fetch]"r"(at->flags[AC_ACCESS_FETCH]), + [user_ds]"i"(32+3), + [user_cs]"i"(24+3), + [user_stack_top]"r"(user_stack + sizeof user_stack), + [kernel_entry_vector]"i"(0x20) + : "rsi"); + + asm volatile (".section .text.pf \n\t" + "page_fault: \n\t" + "pop %rbx \n\t" + "mov %rsi, (%rsp) \n\t" + "movl $1, %eax \n\t" + "iretq \n\t" + ".section .text"); + + asm volatile (".section .text.entry \n\t" + "kernel_entry: \n\t" + "mov %rdx, %rsp \n\t" + "jmp back_to_kernel \n\t" + ".section .text"); + + ac_test_check(at, &success, fault && !at->expected_fault, + "unexpected fault"); + ac_test_check(at, &success, !fault && at->expected_fault, + "unexpected access"); + ac_test_check(at, &success, fault && e != at->expected_error, + "error code %x expected %x", e, at->expected_error); + ac_test_check(at, &success, at->ptep && *at->ptep != at->expected_pte, + "pte %x expected %x", *at->ptep, at->expected_pte); + ac_test_check(at, &success, + !pt_match(*at->pdep, at->expected_pde, at->ignore_pde), + "pde %x expected %x", *at->pdep, at->expected_pde); + +done: + if (success && verbose) { + printf("PASS\n"); + } + return success; +} + +static void ac_test_show(ac_test_t *at) +{ + char line[5000]; + + *line = 0; + strcat(line, "test"); + for (int i = 0; i < NR_AC_FLAGS; ++i) + if (at->flags[i]) { + strcat(line, " "); + strcat(line, ac_names[i]); + } + strcat(line, ": "); + printf("%s", line); +} + +/* + * This test case is used to triger the bug which is fixed by + * commit e09e90a5 in the kvm tree + */ +static int corrupt_hugepage_triger(ac_pool_t *pool) +{ + ac_test_t at1, at2; + + ac_test_init(&at1, (void *)(0x123400000000)); + ac_test_init(&at2, (void *)(0x666600000000)); + + at2.flags[AC_CPU_CR0_WP] = 1; + at2.flags[AC_PDE_PSE] = 1; + at2.flags[AC_PDE_PRESENT] = 1; + ac_test_setup_pte(&at2, pool); + if (!ac_test_do_access(&at2)) + goto err; + + at1.flags[AC_CPU_CR0_WP] = 1; + at1.flags[AC_PDE_PSE] = 1; + at1.flags[AC_PDE_WRITABLE] = 1; + at1.flags[AC_PDE_PRESENT] = 1; + ac_test_setup_pte(&at1, pool); + if (!ac_test_do_access(&at1)) + goto err; + + at1.flags[AC_ACCESS_WRITE] = 1; + ac_set_expected_status(&at1); + if (!ac_test_do_access(&at1)) + goto err; + + at2.flags[AC_ACCESS_WRITE] = 1; + ac_set_expected_status(&at2); + if (!ac_test_do_access(&at2)) + goto err; + + return 1; + +err: + printf("corrupt_hugepage_triger test fail\n"); + return 0; +} + +/* + * This test case is used to triger the bug which is fixed by + * commit 3ddf6c06e13e in the kvm tree + */ +static int check_pfec_on_prefetch_pte(ac_pool_t *pool) +{ + ac_test_t at1, at2; + + ac_test_init(&at1, (void *)(0x123406001000)); + ac_test_init(&at2, (void *)(0x123406003000)); + + at1.flags[AC_PDE_PRESENT] = 1; + at1.flags[AC_PTE_PRESENT] = 1; + ac_setup_specific_pages(&at1, pool, 30 * 1024 * 1024, 30 * 1024 * 1024); + + at2.flags[AC_PDE_PRESENT] = 1; + at2.flags[AC_PTE_NX] = 1; + at2.flags[AC_PTE_PRESENT] = 1; + ac_setup_specific_pages(&at2, pool, 30 * 1024 * 1024, 30 * 1024 * 1024); + + if (!ac_test_do_access(&at1)) { + printf("%s: prepare fail\n", __FUNCTION__); + goto err; + } + + if (!ac_test_do_access(&at2)) { + printf("%s: check PFEC on prefetch pte path fail\n", + __FUNCTION__); + goto err; + } + + return 1; + +err: + return 0; +} + +/* + * If the write-fault access is from supervisor and CR0.WP is not set on the + * vcpu, kvm will fix it by adjusting pte access - it sets the W bit on pte + * and clears U bit. This is the chance that kvm can change pte access from + * readonly to writable. + * + * Unfortunately, the pte access is the access of 'direct' shadow page table, + * means direct sp.role.access = pte_access, then we will create a writable + * spte entry on the readonly shadow page table. It will cause Dirty bit is + * not tracked when two guest ptes point to the same large page. Note, it + * does not have other impact except Dirty bit since cr0.wp is encoded into + * sp.role. + * + * Note: to trigger this bug, hugepage should be disabled on host. + */ +static int check_large_pte_dirty_for_nowp(ac_pool_t *pool) +{ + ac_test_t at1, at2; + + ac_test_init(&at1, (void *)(0x123403000000)); + ac_test_init(&at2, (void *)(0x666606000000)); + + at2.flags[AC_PDE_PRESENT] = 1; + at2.flags[AC_PDE_PSE] = 1; + + ac_test_setup_pte(&at2, pool); + if (!ac_test_do_access(&at2)) { + printf("%s: read on the first mapping fail.\n", __FUNCTION__); + goto err; + } + + at1.flags[AC_PDE_PRESENT] = 1; + at1.flags[AC_PDE_PSE] = 1; + at1.flags[AC_ACCESS_WRITE] = 1; + + ac_test_setup_pte(&at1, pool); + if (!ac_test_do_access(&at1)) { + printf("%s: write on the second mapping fail.\n", __FUNCTION__); + goto err; + } + + at2.flags[AC_ACCESS_WRITE] = 1; + ac_set_expected_status(&at2); + if (!ac_test_do_access(&at2)) { + printf("%s: write on the first mapping fail.\n", __FUNCTION__); + goto err; + } + + return 1; + +err: + return 0; +} + +static int check_smep_andnot_wp(ac_pool_t *pool) +{ + ac_test_t at1; + int err_prepare_andnot_wp, err_smep_andnot_wp; + extern u64 ptl2[]; + + ac_test_init(&at1, (void *)(0x123406001000)); + + at1.flags[AC_PDE_PRESENT] = 1; + at1.flags[AC_PTE_PRESENT] = 1; + at1.flags[AC_PDE_USER] = 1; + at1.flags[AC_PTE_USER] = 1; + at1.flags[AC_PDE_ACCESSED] = 1; + at1.flags[AC_PTE_ACCESSED] = 1; + at1.flags[AC_CPU_CR4_SMEP] = 1; + at1.flags[AC_CPU_CR0_WP] = 0; + at1.flags[AC_ACCESS_WRITE] = 1; + ac_test_setup_pte(&at1, pool); + ptl2[2] -= 0x4; + + /* + * Here we write the ro user page when + * cr0.wp=0, then we execute it and SMEP + * fault should happen. + */ + err_prepare_andnot_wp = ac_test_do_access(&at1); + if (!err_prepare_andnot_wp) { + printf("%s: SMEP prepare fail\n", __FUNCTION__); + goto clean_up; + } + + at1.flags[AC_ACCESS_WRITE] = 0; + at1.flags[AC_ACCESS_FETCH] = 1; + ac_set_expected_status(&at1); + err_smep_andnot_wp = ac_test_do_access(&at1); + +clean_up: + set_cr4_smep(0); + ptl2[2] += 0x4; + + if (!err_prepare_andnot_wp) + goto err; + if (!err_smep_andnot_wp) { + printf("%s: check SMEP without wp fail\n", __FUNCTION__); + goto err; + } + return 1; + +err: + return 0; +} + +int ac_test_exec(ac_test_t *at, ac_pool_t *pool) +{ + int r; + + if (verbose) { + ac_test_show(at); + } + ac_test_setup_pte(at, pool); + r = ac_test_do_access(at); + return r; +} + +typedef int (*ac_test_fn)(ac_pool_t *pool); +const ac_test_fn ac_test_cases[] = +{ + corrupt_hugepage_triger, + check_pfec_on_prefetch_pte, + check_large_pte_dirty_for_nowp, + check_smep_andnot_wp +}; + +int ac_test_run(void) +{ + ac_test_t at; + ac_pool_t pool; + int i, tests, successes; + extern u64 ptl2[]; + + printf("run\n"); + tests = successes = 0; + ac_env_int(&pool); + ac_test_init(&at, (void *)(0x123400000000 + 16 * smp_id())); + do { + if (at.flags[AC_CPU_CR4_SMEP] && (ptl2[2] & 0x4)) + ptl2[2] -= 0x4; + if (!at.flags[AC_CPU_CR4_SMEP] && !(ptl2[2] & 0x4)) { + set_cr4_smep(0); + ptl2[2] += 0x4; + } + + ++tests; + successes += ac_test_exec(&at, &pool); + } while (ac_test_bump(&at)); + + set_cr4_smep(0); + ptl2[2] += 0x4; + + for (i = 0; i < ARRAY_SIZE(ac_test_cases); i++) { + ++tests; + successes += ac_test_cases[i](&pool); + } + + printf("\n%d tests, %d failures\n", tests, tests - successes); + + return successes == tests; +} + +int main() +{ + int r; + + printf("starting test\n\n"); + r = ac_test_run(); + return r ? 0 : 1; +} diff --git a/kvm-unittest/x86/apic.c b/kvm-unittest/x86/apic.c new file mode 100644 index 0000000..50e77fc --- /dev/null +++ b/kvm-unittest/x86/apic.c @@ -0,0 +1,344 @@ +#include "libcflat.h" +#include "apic.h" +#include "vm.h" +#include "smp.h" +#include "desc.h" +#include "isr.h" + +static int g_fail; +static int g_tests; + +static void report(const char *msg, int pass) +{ + ++g_tests; + printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL")); + if (!pass) + ++g_fail; +} + +static void test_lapic_existence(void) +{ + u32 lvr; + + lvr = apic_read(APIC_LVR); + printf("apic version: %x\n", lvr); + report("apic existence", (u16)lvr == 0x14); +} + +#define TSC_DEADLINE_TIMER_MODE (2 << 17) +#define TSC_DEADLINE_TIMER_VECTOR 0xef +#define MSR_IA32_TSC 0x00000010 +#define MSR_IA32_TSCDEADLINE 0x000006e0 + +static int tdt_count; + +static void tsc_deadline_timer_isr(isr_regs_t *regs) +{ + ++tdt_count; +} + +static void start_tsc_deadline_timer(void) +{ + handle_irq(TSC_DEADLINE_TIMER_VECTOR, tsc_deadline_timer_isr); + irq_enable(); + + wrmsr(MSR_IA32_TSCDEADLINE, rdmsr(MSR_IA32_TSC)); + asm volatile ("nop"); + report("tsc deadline timer", tdt_count == 1); +} + +static int enable_tsc_deadline_timer(void) +{ + uint32_t lvtt; + + if (cpuid(1).c & (1 << 24)) { + lvtt = TSC_DEADLINE_TIMER_MODE | TSC_DEADLINE_TIMER_VECTOR; + apic_write(APIC_LVTT, lvtt); + start_tsc_deadline_timer(); + return 1; + } else { + return 0; + } +} + +static void test_tsc_deadline_timer(void) +{ + if(enable_tsc_deadline_timer()) { + printf("tsc deadline timer enabled\n"); + } else { + printf("tsc deadline timer not detected\n"); + } +} + +#define MSR_APIC_BASE 0x0000001b + +void test_enable_x2apic(void) +{ + if (enable_x2apic()) { + printf("x2apic enabled\n"); + } else { + printf("x2apic not detected\n"); + } +} + +static void eoi(void) +{ + apic_write(APIC_EOI, 0); +} + +static int ipi_count; + +static void self_ipi_isr(isr_regs_t *regs) +{ + ++ipi_count; + eoi(); +} + +static void test_self_ipi(void) +{ + int vec = 0xf1; + + handle_irq(vec, self_ipi_isr); + irq_enable(); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | vec, + 0); + asm volatile ("nop"); + report("self ipi", ipi_count == 1); +} + +static void set_ioapic_redir(unsigned line, unsigned vec) +{ + ioapic_redir_entry_t e = { + .vector = vec, + .delivery_mode = 0, + .trig_mode = 0, + }; + + ioapic_write_redir(line, e); +} + +static void set_irq_line(unsigned line, int val) +{ + asm volatile("out %0, %1" : : "a"((u8)val), "d"((u16)(0x2000 + line))); +} + +static void toggle_irq_line(unsigned line) +{ + set_irq_line(line, 1); + set_irq_line(line, 0); +} + +static int g_isr_77; + +static void ioapic_isr_77(isr_regs_t *regs) +{ + ++g_isr_77; + eoi(); +} + +static void test_ioapic_intr(void) +{ + handle_irq(0x77, ioapic_isr_77); + set_ioapic_redir(0x0e, 0x77); + toggle_irq_line(0x0e); + asm volatile ("nop"); + report("ioapic interrupt", g_isr_77 == 1); +} + +static int g_78, g_66, g_66_after_78; +static ulong g_66_rip, g_78_rip; + +static void ioapic_isr_78(isr_regs_t *regs) +{ + ++g_78; + g_78_rip = regs->rip; + eoi(); +} + +static void ioapic_isr_66(isr_regs_t *regs) +{ + ++g_66; + if (g_78) + ++g_66_after_78; + g_66_rip = regs->rip; + eoi(); +} + +static void test_ioapic_simultaneous(void) +{ + handle_irq(0x78, ioapic_isr_78); + handle_irq(0x66, ioapic_isr_66); + set_ioapic_redir(0x0e, 0x78); + set_ioapic_redir(0x0f, 0x66); + irq_disable(); + toggle_irq_line(0x0f); + toggle_irq_line(0x0e); + irq_enable(); + asm volatile ("nop"); + report("ioapic simultaneous interrupt", + g_66 && g_78 && g_66_after_78 && g_66_rip == g_78_rip); +} + +volatile int nmi_counter_private, nmi_counter, nmi_hlt_counter, sti_loop_active; + +void sti_nop(char *p) +{ + asm volatile ( + ".globl post_sti \n\t" + "sti \n" + /* + * vmx won't exit on external interrupt if blocked-by-sti, + * so give it a reason to exit by accessing an unmapped page. + */ + "post_sti: testb $0, %0 \n\t" + "nop \n\t" + "cli" + : : "m"(*p) + ); + nmi_counter = nmi_counter_private; +} + +static void sti_loop(void *ignore) +{ + unsigned k = 0; + + while (sti_loop_active) { + sti_nop((char *)(ulong)((k++ * 4096) % (128 * 1024 * 1024))); + } +} + +static void nmi_handler(isr_regs_t *regs) +{ + extern void post_sti(void); + ++nmi_counter_private; + nmi_hlt_counter += regs->rip == (ulong)post_sti; +} + +static void update_cr3(void *cr3) +{ + write_cr3((ulong)cr3); +} + +static void test_sti_nmi(void) +{ + unsigned old_counter; + + if (cpu_count() < 2) { + return; + } + + handle_irq(2, nmi_handler); + on_cpu(1, update_cr3, (void *)read_cr3()); + + sti_loop_active = 1; + on_cpu_async(1, sti_loop, 0); + while (nmi_counter < 30000) { + old_counter = nmi_counter; + apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 1); + while (nmi_counter == old_counter) { + ; + } + } + sti_loop_active = 0; + report("nmi-after-sti", nmi_hlt_counter == 0); +} + +static volatile bool nmi_done, nmi_flushed; +static volatile int nmi_received; +static volatile int cpu0_nmi_ctr1, cpu1_nmi_ctr1; +static volatile int cpu0_nmi_ctr2, cpu1_nmi_ctr2; + +static void multiple_nmi_handler(isr_regs_t *regs) +{ + ++nmi_received; +} + +static void kick_me_nmi(void *blah) +{ + while (!nmi_done) { + ++cpu1_nmi_ctr1; + while (cpu1_nmi_ctr1 != cpu0_nmi_ctr1 && !nmi_done) { + pause(); + } + if (nmi_done) { + return; + } + apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0); + /* make sure the NMI has arrived by sending an IPI after it */ + apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_FIXED | APIC_INT_ASSERT + | 0x44, 0); + ++cpu1_nmi_ctr2; + while (cpu1_nmi_ctr2 != cpu0_nmi_ctr2 && !nmi_done) { + pause(); + } + } +} + +static void flush_nmi(isr_regs_t *regs) +{ + nmi_flushed = true; + apic_write(APIC_EOI, 0); +} + +static void test_multiple_nmi(void) +{ + int i; + bool ok = true; + + if (cpu_count() < 2) { + return; + } + + sti(); + handle_irq(2, multiple_nmi_handler); + handle_irq(0x44, flush_nmi); + on_cpu_async(1, kick_me_nmi, 0); + for (i = 0; i < 1000000; ++i) { + nmi_flushed = false; + nmi_received = 0; + ++cpu0_nmi_ctr1; + while (cpu1_nmi_ctr1 != cpu0_nmi_ctr1) { + pause(); + } + apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0); + while (!nmi_flushed) { + pause(); + } + if (nmi_received != 2) { + ok = false; + break; + } + ++cpu0_nmi_ctr2; + while (cpu1_nmi_ctr2 != cpu0_nmi_ctr2) { + pause(); + } + } + nmi_done = true; + report("multiple nmi", ok); +} + +int main() +{ + setup_vm(); + smp_init(); + setup_idt(); + + test_lapic_existence(); + + mask_pic_interrupts(); + enable_apic(); + test_enable_x2apic(); + + test_self_ipi(); + + test_ioapic_intr(); + test_ioapic_simultaneous(); + test_sti_nmi(); + test_multiple_nmi(); + + test_tsc_deadline_timer(); + + printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail); + + return g_fail != 0; +} diff --git a/kvm-unittest/x86/asyncpf.c b/kvm-unittest/x86/asyncpf.c new file mode 100644 index 0000000..95e7741 --- /dev/null +++ b/kvm-unittest/x86/asyncpf.c @@ -0,0 +1,113 @@ +/* + * Async PF test. For the test to actually do anything it needs to be started + * in memory cgroup with 512M of memory and with more then 1G memory provided + * to the guest. + * + * To create cgroup do as root: + * mkdir /dev/cgroup + * mount -t cgroup none -omemory /dev/cgroup + * chmod a+rxw /dev/cgroup/ + * + * From a shell you will start qemu from: + * mkdir /dev/cgroup/1 + * echo $$ > /dev/cgroup/1/tasks + * echo 512M > /dev/cgroup/1/memory.limit_in_bytes + * + */ +#include "x86/msr.h" +#include "x86/processor.h" +#include "x86/apic-defs.h" +#include "x86/apic.h" +#include "x86/desc.h" +#include "x86/isr.h" +#include "x86/vm.h" + +#include "libcflat.h" +#include + +#define KVM_PV_REASON_PAGE_NOT_PRESENT 1 +#define KVM_PV_REASON_PAGE_READY 2 + +#define MSR_KVM_ASYNC_PF_EN 0x4b564d02 + +#define KVM_ASYNC_PF_ENABLED (1 << 0) +#define KVM_ASYNC_PF_SEND_ALWAYS (1 << 1) + +volatile uint32_t apf_reason __attribute__((aligned(64))); +char *buf; +volatile uint64_t i; +volatile uint64_t phys; +bool fail; + +static inline uint32_t get_apf_reason(void) +{ + uint32_t r = apf_reason; + apf_reason = 0; + return r; +} + +static void pf_isr(struct ex_regs *r) +{ + void* virt = (void*)((ulong)(buf+i) & ~(PAGE_SIZE-1)); + uint32_t reason = get_apf_reason(); + + switch (reason) { + case 0: + printf("unexpected #PF at %p\n", read_cr2()); + fail = true; + 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()); + printf("Got not present #PF token %x virt addr %p phys addr %p\n", read_cr2(), virt, phys); + while(phys) { + safe_halt(); /* enables irq */ + irq_disable(); + } + break; + case KVM_PV_REASON_PAGE_READY: + printf("Got present #PF token %x\n", read_cr2()); + if ((uint32_t)read_cr2() == ~0) + break; + install_pte(phys_to_virt(read_cr3()), 1, virt, phys | PTE_PRESENT | PTE_WRITE, 0); + write_cr3(read_cr3()); + phys = 0; + break; + default: + printf("unexpected async pf reason %d\n", reason); + fail = true; + break; + } +} + +#define MEM 1ull*1024*1024*1024 + +int main(int ac, char **av) +{ + int loop = 2; + + setup_vm(); + setup_idt(); + setup_gdt(); + printf("install handler\n"); + handle_exception(14, pf_isr); + apf_reason = 0; + printf("enable async pf\n"); + wrmsr(MSR_KVM_ASYNC_PF_EN, virt_to_phys((void*)&apf_reason) | + KVM_ASYNC_PF_SEND_ALWAYS | KVM_ASYNC_PF_ENABLED); + printf("alloc memory\n"); + buf = vmalloc(MEM); + irq_enable(); + while(loop--) { + printf("start loop\n"); + /* access a lot of memory to make host swap it out */ + for (i=0; i < MEM; i+=4096) + buf[i] = 1; + printf("end loop\n"); + } + irq_disable(); + + printf("%s\n", fail ? "FAIL" : "PASS"); + return fail; +} diff --git a/kvm-unittest/x86/emulator.c b/kvm-unittest/x86/emulator.c new file mode 100644 index 0000000..68d2b93 --- /dev/null +++ b/kvm-unittest/x86/emulator.c @@ -0,0 +1,1024 @@ +#include "ioram.h" +#include "vm.h" +#include "libcflat.h" +#include "desc.h" +#include "types.h" + +#define memset __builtin_memset +#define TESTDEV_IO_PORT 0xe0 + +int fails, tests; + +static int exceptions; + +struct regs { + u64 rax, rbx, rcx, rdx; + u64 rsi, rdi, rsp, rbp; + u64 r8, r9, r10, r11; + u64 r12, r13, r14, r15; + u64 rip, rflags; +}; +struct regs inregs, outregs, save; + +struct insn_desc { + u64 ptr; + size_t len; +}; + +void report(const char *name, int result) +{ + ++tests; + if (result) + printf("PASS: %s\n", name); + else { + printf("FAIL: %s\n", name); + ++fails; + } +} + +static char st1[] = "abcdefghijklmnop"; + +void test_stringio() +{ + unsigned char r = 0; + asm volatile("cld \n\t" + "movw %0, %%dx \n\t" + "rep outsb \n\t" + : : "i"((short)TESTDEV_IO_PORT), + "S"(st1), "c"(sizeof(st1) - 1)); + asm volatile("inb %1, %0\n\t" : "=a"(r) : "i"((short)TESTDEV_IO_PORT)); + report("outsb up", r == st1[sizeof(st1) - 2]); /* last char */ + + asm volatile("std \n\t" + "movw %0, %%dx \n\t" + "rep outsb \n\t" + : : "i"((short)TESTDEV_IO_PORT), + "S"(st1 + sizeof(st1) - 2), "c"(sizeof(st1) - 1)); + asm volatile("cld \n\t" : : ); + asm volatile("in %1, %0\n\t" : "=a"(r) : "i"((short)TESTDEV_IO_PORT)); + report("outsb down", r == st1[0]); +} + +void test_cmps_one(unsigned char *m1, unsigned char *m3) +{ + void *rsi, *rdi; + long rcx, tmp; + + rsi = m1; rdi = m3; rcx = 30; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsb" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsb (1)", rcx == 0 && rsi == m1 + 30 && rdi == m3 + 30); + + rsi = m1; rdi = m3; rcx = 30; + asm volatile("or $1, %[tmp]\n\t" // clear ZF + "repe/cmpsb" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsb (1.zf)", rcx == 0 && rsi == m1 + 30 && rdi == m3 + 30); + + rsi = m1; rdi = m3; rcx = 15; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsw" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsw (1)", rcx == 0 && rsi == m1 + 30 && rdi == m3 + 30); + + rsi = m1; rdi = m3; rcx = 7; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsl" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpll (1)", rcx == 0 && rsi == m1 + 28 && rdi == m3 + 28); + + rsi = m1; rdi = m3; rcx = 4; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsq" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsq (1)", rcx == 0 && rsi == m1 + 32 && rdi == m3 + 32); + + rsi = m1; rdi = m3; rcx = 130; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsb" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsb (2)", + rcx == 29 && rsi == m1 + 101 && rdi == m3 + 101); + + rsi = m1; rdi = m3; rcx = 65; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsw" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsw (2)", + rcx == 14 && rsi == m1 + 102 && rdi == m3 + 102); + + rsi = m1; rdi = m3; rcx = 32; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsl" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpll (2)", + rcx == 6 && rsi == m1 + 104 && rdi == m3 + 104); + + rsi = m1; rdi = m3; rcx = 16; + asm volatile("xor %[tmp], %[tmp] \n\t" + "repe/cmpsq" + : "+S"(rsi), "+D"(rdi), "+c"(rcx), [tmp]"=&r"(tmp) + : : "cc"); + report("repe/cmpsq (2)", + rcx == 3 && rsi == m1 + 104 && rdi == m3 + 104); + +} + +void test_cmps(void *mem) +{ + unsigned char *m1 = mem, *m2 = mem + 1024; + unsigned char m3[1024]; + + for (int i = 0; i < 100; ++i) + m1[i] = m2[i] = m3[i] = i; + for (int i = 100; i < 200; ++i) + m1[i] = (m3[i] = m2[i] = i) + 1; + test_cmps_one(m1, m3); + test_cmps_one(m1, m2); +} + +void test_scas(void *mem) +{ + bool z; + void *di; + + *(ulong *)mem = 0x77665544332211; + + di = mem; + asm ("scasb; setz %0" : "=rm"(z), "+D"(di) : "a"(0xff11)); + report("scasb match", di == mem + 1 && z); + + di = mem; + asm ("scasb; setz %0" : "=rm"(z), "+D"(di) : "a"(0xff54)); + report("scasb mismatch", di == mem + 1 && !z); + + di = mem; + asm ("scasw; setz %0" : "=rm"(z), "+D"(di) : "a"(0xff2211)); + report("scasw match", di == mem + 2 && z); + + di = mem; + asm ("scasw; setz %0" : "=rm"(z), "+D"(di) : "a"(0xffdd11)); + report("scasw mismatch", di == mem + 2 && !z); + + di = mem; + asm ("scasl; setz %0" : "=rm"(z), "+D"(di) : "a"(0xff44332211ul)); + report("scasd match", di == mem + 4 && z); + + di = mem; + asm ("scasl; setz %0" : "=rm"(z), "+D"(di) : "a"(0x45332211)); + report("scasd mismatch", di == mem + 4 && !z); + + di = mem; + asm ("scasq; setz %0" : "=rm"(z), "+D"(di) : "a"(0x77665544332211ul)); + report("scasq match", di == mem + 8 && z); + + di = mem; + asm ("scasq; setz %0" : "=rm"(z), "+D"(di) : "a"(3)); + report("scasq mismatch", di == mem + 8 && !z); +} + +void test_cr8(void) +{ + unsigned long src, dst; + + dst = 777; + src = 3; + asm volatile("mov %[src], %%cr8; mov %%cr8, %[dst]" + : [dst]"+r"(dst), [src]"+r"(src)); + report("mov %cr8", dst == 3 && src == 3); +} + +void test_push(void *mem) +{ + unsigned long tmp; + unsigned long *stack_top = mem + 4096; + unsigned long *new_stack_top; + unsigned long memw = 0x123456789abcdeful; + + memset(mem, 0x55, (void *)stack_top - mem); + + asm volatile("mov %%rsp, %[tmp] \n\t" + "mov %[stack_top], %%rsp \n\t" + "pushq $-7 \n\t" + "pushq %[reg] \n\t" + "pushq (%[mem]) \n\t" + "pushq $-7070707 \n\t" + "mov %%rsp, %[new_stack_top] \n\t" + "mov %[tmp], %%rsp" + : [tmp]"=&r"(tmp), [new_stack_top]"=r"(new_stack_top) + : [stack_top]"r"(stack_top), + [reg]"r"(-17l), [mem]"r"(&memw) + : "memory"); + + report("push $imm8", stack_top[-1] == -7ul); + report("push %reg", stack_top[-2] == -17ul); + report("push mem", stack_top[-3] == 0x123456789abcdeful); + report("push $imm", stack_top[-4] == -7070707); +} + +void test_pop(void *mem) +{ + unsigned long tmp, tmp3, rsp, rbp; + unsigned long *stack_top = mem + 4096; + unsigned long memw = 0x123456789abcdeful; + static unsigned long tmp2; + + memset(mem, 0x55, (void *)stack_top - mem); + + asm volatile("pushq %[val] \n\t" + "popq (%[mem])" + : : [val]"m"(memw), [mem]"r"(mem) : "memory"); + report("pop mem", *(unsigned long *)mem == memw); + + memw = 7 - memw; + asm volatile("mov %%rsp, %[tmp] \n\t" + "mov %[stack_top], %%rsp \n\t" + "pushq %[val] \n\t" + "popq %[tmp2] \n\t" + "mov %[tmp], %%rsp" + : [tmp]"=&r"(tmp), [tmp2]"=m"(tmp2) + : [val]"r"(memw), [stack_top]"r"(stack_top) + : "memory"); + report("pop mem (2)", tmp2 == memw); + + memw = 129443 - memw; + asm volatile("mov %%rsp, %[tmp] \n\t" + "mov %[stack_top], %%rsp \n\t" + "pushq %[val] \n\t" + "popq %[tmp2] \n\t" + "mov %[tmp], %%rsp" + : [tmp]"=&r"(tmp), [tmp2]"=r"(tmp2) + : [val]"r"(memw), [stack_top]"r"(stack_top) + : "memory"); + report("pop reg", tmp2 == memw); + + asm volatile("mov %%rsp, %[tmp] \n\t" + "mov %[stack_top], %%rsp \n\t" + "push $1f \n\t" + "ret \n\t" + "2: jmp 2b \n\t" + "1: mov %[tmp], %%rsp" + : [tmp]"=&r"(tmp) : [stack_top]"r"(stack_top) + : "memory"); + report("ret", 1); + + stack_top[-1] = 0x778899; + asm volatile("mov %%rsp, %[tmp] \n\t" + "mov %%rbp, %[tmp3] \n\t" + "mov %[stack_top], %%rbp \n\t" + "leave \n\t" + "xchg %%rsp, %[tmp] \n\t" + "xchg %%rbp, %[tmp3]" + : [tmp]"=&r"(tmp), [tmp3]"=&r"(tmp3) : [stack_top]"r"(stack_top-1) + : "memory"); + report("leave", tmp == (ulong)stack_top && tmp3 == 0x778899); + + rbp = 0xaa55aa55bb66bb66ULL; + rsp = (unsigned long)stack_top; + asm volatile("xchg %%rsp, %[rsp] \n\t" + "xchg %%rbp, %[rbp] \n\t" + "enter $0x1238, $0 \n\t" + "xchg %%rsp, %[rsp] \n\t" + "xchg %%rbp, %[rbp]" + : [rsp]"+a"(rsp), [rbp]"+b"(rbp) : : "memory"); + report("enter", + rsp == (unsigned long)stack_top - 8 - 0x1238 + && rbp == (unsigned long)stack_top - 8 + && stack_top[-1] == 0xaa55aa55bb66bb66ULL); +} + +void test_ljmp(void *mem) +{ + unsigned char *m = mem; + volatile int res = 1; + + *(unsigned long**)m = &&jmpf; + asm volatile ("data16/mov %%cs, %0":"=m"(*(m + sizeof(unsigned long)))); + asm volatile ("rex64/ljmp *%0"::"m"(*m)); + res = 0; +jmpf: + report("ljmp", res); +} + +void test_incdecnotneg(void *mem) +{ + unsigned long *m = mem, v = 1234; + unsigned char *mb = mem, vb = 66; + + *m = 0; + + asm volatile ("incl %0":"+m"(*m)); + report("incl", *m == 1); + asm volatile ("decl %0":"+m"(*m)); + report("decl", *m == 0); + asm volatile ("incb %0":"+m"(*m)); + report("incb", *m == 1); + asm volatile ("decb %0":"+m"(*m)); + report("decb", *m == 0); + + asm volatile ("lock incl %0":"+m"(*m)); + report("lock incl", *m == 1); + asm volatile ("lock decl %0":"+m"(*m)); + report("lock decl", *m == 0); + asm volatile ("lock incb %0":"+m"(*m)); + report("lock incb", *m == 1); + asm volatile ("lock decb %0":"+m"(*m)); + report("lock decb", *m == 0); + + *m = v; + + asm ("lock negq %0" : "+m"(*m)); v = -v; + report("lock negl", *m == v); + asm ("lock notq %0" : "+m"(*m)); v = ~v; + report("lock notl", *m == v); + + *mb = vb; + + asm ("lock negb %0" : "+m"(*mb)); vb = -vb; + report("lock negb", *mb == vb); + asm ("lock notb %0" : "+m"(*mb)); vb = ~vb; + report("lock notb", *mb == vb); +} + +void test_smsw(void) +{ + char mem[16]; + unsigned short msw, msw_orig, *pmsw; + int i, zero; + + msw_orig = read_cr0(); + + asm("smsw %0" : "=r"(msw)); + report("smsw (1)", msw == msw_orig); + + memset(mem, 0, 16); + pmsw = (void *)mem; + asm("smsw %0" : "=m"(pmsw[4])); + zero = 1; + for (i = 0; i < 8; ++i) + if (i != 4 && pmsw[i]) + zero = 0; + report("smsw (2)", msw == pmsw[4] && zero); +} + +void test_lmsw(void) +{ + char mem[16]; + unsigned short msw, *pmsw; + unsigned long cr0; + + cr0 = read_cr0(); + + msw = cr0 ^ 8; + asm("lmsw %0" : : "r"(msw)); + printf("before %lx after %lx\n", cr0, read_cr0()); + report("lmsw (1)", (cr0 ^ read_cr0()) == 8); + + pmsw = (void *)mem; + *pmsw = cr0; + asm("lmsw %0" : : "m"(*pmsw)); + printf("before %lx after %lx\n", cr0, read_cr0()); + report("lmsw (2)", cr0 == read_cr0()); + + /* lmsw can't clear cr0.pe */ + msw = (cr0 & ~1ul) ^ 4; /* change EM to force trap */ + asm("lmsw %0" : : "r"(msw)); + report("lmsw (3)", (cr0 ^ read_cr0()) == 4 && (cr0 & 1)); + + /* back to normal */ + msw = cr0; + asm("lmsw %0" : : "r"(msw)); +} + +void test_xchg(void *mem) +{ + unsigned long *memq = mem; + unsigned long rax; + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xchg %%al, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xchg reg, r/m (1)", + rax == 0xfedcba98765432ef && *memq == 0x123456789abcd10); + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xchg %%ax, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xchg reg, r/m (2)", + rax == 0xfedcba987654cdef && *memq == 0x123456789ab3210); + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xchg %%eax, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xchg reg, r/m (3)", + rax == 0x89abcdef && *memq == 0x123456776543210); + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xchg %%rax, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xchg reg, r/m (4)", + rax == 0x123456789abcdef && *memq == 0xfedcba9876543210); +} + +void test_xadd(void *mem) +{ + unsigned long *memq = mem; + unsigned long rax; + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xadd %%al, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xadd reg, r/m (1)", + rax == 0xfedcba98765432ef && *memq == 0x123456789abcdff); + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xadd %%ax, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xadd reg, r/m (2)", + rax == 0xfedcba987654cdef && *memq == 0x123456789abffff); + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xadd %%eax, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xadd reg, r/m (3)", + rax == 0x89abcdef && *memq == 0x1234567ffffffff); + + asm volatile("mov $0x123456789abcdef, %%rax\n\t" + "mov %%rax, (%[memq])\n\t" + "mov $0xfedcba9876543210, %%rax\n\t" + "xadd %%rax, (%[memq])\n\t" + "mov %%rax, %[rax]\n\t" + : [rax]"=r"(rax) + : [memq]"r"(memq) + : "memory"); + report("xadd reg, r/m (4)", + rax == 0x123456789abcdef && *memq == 0xffffffffffffffff); +} + +void test_btc(void *mem) +{ + unsigned int *a = mem; + + memset(mem, 0, 3 * sizeof(unsigned int)); + + asm ("btcl $32, %0" :: "m"(a[0]) : "memory"); + asm ("btcl $1, %0" :: "m"(a[1]) : "memory"); + asm ("btcl %1, %0" :: "m"(a[0]), "r"(66) : "memory"); + report("btcl imm8, r/m", a[0] == 1 && a[1] == 2 && a[2] == 4); + + asm ("btcl %1, %0" :: "m"(a[3]), "r"(-1) : "memory"); + report("btcl reg, r/m", a[0] == 1 && a[1] == 2 && a[2] == 0x80000004); +} + +void test_bsfbsr(void *mem) +{ + unsigned long rax, *memq = mem; + unsigned eax, *meml = mem; + unsigned short ax, *memw = mem; + unsigned char z; + + *memw = 0xc000; + asm("bsfw %[mem], %[a]" : [a]"=a"(ax) : [mem]"m"(*memw)); + report("bsfw r/m, reg", ax == 14); + + *meml = 0xc0000000; + asm("bsfl %[mem], %[a]" : [a]"=a"(eax) : [mem]"m"(*meml)); + report("bsfl r/m, reg", eax == 30); + + *memq = 0xc00000000000; + asm("bsfq %[mem], %[a]" : [a]"=a"(rax) : [mem]"m"(*memq)); + report("bsfq r/m, reg", rax == 46); + + *memq = 0; + asm("bsfq %[mem], %[a]; setz %[z]" + : [a]"=a"(rax), [z]"=rm"(z) : [mem]"m"(*memq)); + report("bsfq r/m, reg", z == 1); + + *memw = 0xc000; + asm("bsrw %[mem], %[a]" : [a]"=a"(ax) : [mem]"m"(*memw)); + report("bsrw r/m, reg", ax == 15); + + *meml = 0xc0000000; + asm("bsrl %[mem], %[a]" : [a]"=a"(eax) : [mem]"m"(*meml)); + report("bsrl r/m, reg", eax == 31); + + *memq = 0xc00000000000; + asm("bsrq %[mem], %[a]" : [a]"=a"(rax) : [mem]"m"(*memq)); + report("bsrq r/m, reg", rax == 47); + + *memq = 0; + asm("bsrq %[mem], %[a]; setz %[z]" + : [a]"=a"(rax), [z]"=rm"(z) : [mem]"m"(*memq)); + report("bsrq r/m, reg", z == 1); +} + +static void test_imul(ulong *mem) +{ + ulong a; + + *mem = 51; a = 0x1234567812345678UL; + asm ("imulw %1, %%ax" : "+a"(a) : "m"(*mem)); + report("imul ax, mem", a == 0x12345678123439e8); + + *mem = 51; a = 0x1234567812345678UL; + asm ("imull %1, %%eax" : "+a"(a) : "m"(*mem)); + report("imul eax, mem", a == 0xa06d39e8); + + *mem = 51; a = 0x1234567812345678UL; + asm ("imulq %1, %%rax" : "+a"(a) : "m"(*mem)); + report("imul rax, mem", a == 0xA06D39EBA06D39E8UL); + + *mem = 0x1234567812345678UL; a = 0x8765432187654321L; + asm ("imulw $51, %1, %%ax" : "+a"(a) : "m"(*mem)); + report("imul ax, mem, imm8", a == 0x87654321876539e8); + + *mem = 0x1234567812345678UL; + asm ("imull $51, %1, %%eax" : "+a"(a) : "m"(*mem)); + report("imul eax, mem, imm8", a == 0xa06d39e8); + + *mem = 0x1234567812345678UL; + asm ("imulq $51, %1, %%rax" : "+a"(a) : "m"(*mem)); + report("imul rax, mem, imm8", a == 0xA06D39EBA06D39E8UL); + + *mem = 0x1234567812345678UL; a = 0x8765432187654321L; + asm ("imulw $311, %1, %%ax" : "+a"(a) : "m"(*mem)); + report("imul ax, mem, imm", a == 0x8765432187650bc8); + + *mem = 0x1234567812345678UL; + asm ("imull $311, %1, %%eax" : "+a"(a) : "m"(*mem)); + report("imul eax, mem, imm", a == 0x1d950bc8); + + *mem = 0x1234567812345678UL; + asm ("imulq $311, %1, %%rax" : "+a"(a) : "m"(*mem)); + report("imul rax, mem, imm", a == 0x1D950BDE1D950BC8L); +} + +static void test_muldiv(long *mem) +{ + long a, d, aa, dd; + u8 ex = 1; + + *mem = 0; a = 1; d = 2; + asm (ASM_TRY("1f") "divq %3; movb $0, %2; 1:" + : "+a"(a), "+d"(d), "+q"(ex) : "m"(*mem)); + report("divq (fault)", a == 1 && d == 2 && ex); + + *mem = 987654321098765UL; a = 123456789012345UL; d = 123456789012345UL; + asm (ASM_TRY("1f") "divq %3; movb $0, %2; 1:" + : "+a"(a), "+d"(d), "+q"(ex) : "m"(*mem)); + report("divq (1)", + a == 0x1ffffffb1b963b33ul && d == 0x273ba4384ede2ul && !ex); + aa = 0x1111111111111111; dd = 0x2222222222222222; + *mem = 0x3333333333333333; a = aa; d = dd; + asm("mulb %2" : "+a"(a), "+d"(d) : "m"(*mem)); + report("mulb mem", a == 0x1111111111110363 && d == dd); + *mem = 0x3333333333333333; a = aa; d = dd; + asm("mulw %2" : "+a"(a), "+d"(d) : "m"(*mem)); + report("mulw mem", a == 0x111111111111c963 && d == 0x2222222222220369); + *mem = 0x3333333333333333; a = aa; d = dd; + asm("mull %2" : "+a"(a), "+d"(d) : "m"(*mem)); + report("mull mem", a == 0x962fc963 && d == 0x369d036); + *mem = 0x3333333333333333; a = aa; d = dd; + asm("mulq %2" : "+a"(a), "+d"(d) : "m"(*mem)); + report("mulq mem", a == 0x2fc962fc962fc963 && d == 0x369d0369d0369d0); +} + +typedef unsigned __attribute__((vector_size(16))) sse128; + +typedef union { + sse128 sse; + unsigned u[4]; +} sse_union; + +static bool sseeq(sse_union *v1, sse_union *v2) +{ + bool ok = true; + int i; + + for (i = 0; i < 4; ++i) { + ok &= v1->u[i] == v2->u[i]; + } + + return ok; +} + +static void test_sse(sse_union *mem) +{ + sse_union v; + + write_cr0(read_cr0() & ~6); /* EM, TS */ + write_cr4(read_cr4() | 0x200); /* OSFXSR */ + v.u[0] = 1; v.u[1] = 2; v.u[2] = 3; v.u[3] = 4; + asm("movdqu %1, %0" : "=m"(*mem) : "x"(v.sse)); + report("movdqu (read)", sseeq(&v, mem)); + mem->u[0] = 5; mem->u[1] = 6; mem->u[2] = 7; mem->u[3] = 8; + asm("movdqu %1, %0" : "=x"(v.sse) : "m"(*mem)); + report("movdqu (write)", sseeq(mem, &v)); +} + +static void test_mmx(uint64_t *mem) +{ + uint64_t v; + + write_cr0(read_cr0() & ~6); /* EM, TS */ + asm volatile("fninit"); + v = 0x0102030405060708ULL; + asm("movq %1, %0" : "=m"(*mem) : "y"(v)); + report("movq (mmx, read)", v == *mem); + *mem = 0x8070605040302010ull; + asm("movq %1, %0" : "=y"(v) : "m"(*mem)); + report("movq (mmx, write)", v == *mem); +} + +static void test_rip_relative(unsigned *mem, char *insn_ram) +{ + /* movb $1, mem+2(%rip) */ + insn_ram[0] = 0xc6; + insn_ram[1] = 0x05; + *(unsigned *)&insn_ram[2] = 2 + (char *)mem - (insn_ram + 7); + insn_ram[6] = 0x01; + /* ret */ + insn_ram[7] = 0xc3; + + *mem = 0; + asm("callq *%1" : "+m"(*mem) : "r"(insn_ram)); + report("movb $imm, 0(%rip)", *mem == 0x10000); +} + +static void test_shld_shrd(u32 *mem) +{ + *mem = 0x12345678; + asm("shld %2, %1, %0" : "+m"(*mem) : "r"(0xaaaaaaaaU), "c"((u8)3)); + report("shld (cl)", *mem == ((0x12345678 << 3) | 5)); + *mem = 0x12345678; + asm("shrd %2, %1, %0" : "+m"(*mem) : "r"(0x55555555U), "c"((u8)3)); + report("shrd (cl)", *mem == ((0x12345678 >> 3) | (5u << 29))); +} + +#define INSN_XCHG_ALL \ + "xchg %rax, 0+save \n\t" \ + "xchg %rbx, 8+save \n\t" \ + "xchg %rcx, 16+save \n\t" \ + "xchg %rdx, 24+save \n\t" \ + "xchg %rsi, 32+save \n\t" \ + "xchg %rdi, 40+save \n\t" \ + "xchg %rsp, 48+save \n\t" \ + "xchg %rbp, 56+save \n\t" \ + "xchg %r8, 64+save \n\t" \ + "xchg %r9, 72+save \n\t" \ + "xchg %r10, 80+save \n\t" \ + "xchg %r11, 88+save \n\t" \ + "xchg %r12, 96+save \n\t" \ + "xchg %r13, 104+save \n\t" \ + "xchg %r14, 112+save \n\t" \ + "xchg %r15, 120+save \n\t" + +asm( + ".align 4096\n\t" + "insn_page:\n\t" + "ret\n\t" + "pushf\n\t" + "push 136+save \n\t" + "popf \n\t" + INSN_XCHG_ALL + "test_insn:\n\t" + "in (%dx),%al\n\t" + ".skip 31, 0x90\n\t" + "test_insn_end:\n\t" + INSN_XCHG_ALL + "pushf \n\t" + "pop 136+save \n\t" + "popf \n\t" + "ret \n\t" + "insn_page_end:\n\t" + ".align 4096\n\t" +); + +#define MK_INSN(name, str) \ + asm ( \ + ".pushsection .data.insn \n\t" \ + "insn_" #name ": \n\t" \ + ".quad 1001f, 1002f - 1001f \n\t" \ + ".popsection \n\t" \ + ".pushsection .text.insn, \"ax\" \n\t" \ + "1001: \n\t" \ + "insn_code_" #name ": " str " \n\t" \ + "1002: \n\t" \ + ".popsection" \ + ); \ + extern struct insn_desc insn_##name; + +static void trap_emulator(uint64_t *mem, void *alt_insn_page, + struct insn_desc *alt_insn) +{ + ulong *cr3 = (ulong *)read_cr3(); + void *insn_ram; + extern u8 insn_page[], test_insn[]; + + insn_ram = vmap(virt_to_phys(insn_page), 4096); + memcpy(alt_insn_page, insn_page, 4096); + memcpy(alt_insn_page + (test_insn - insn_page), + (void *)(alt_insn->ptr), alt_insn->len); + save = inregs; + + /* Load the code TLB with insn_page, but point the page tables at + alt_insn_page (and keep the data TLB clear, for AMD decode assist). + This will make the CPU trap on the insn_page instruction but the + hypervisor will see alt_insn_page. */ + install_page(cr3, virt_to_phys(insn_page), insn_ram); + invlpg(insn_ram); + /* Load code TLB */ + asm volatile("call *%0" : : "r"(insn_ram)); + install_page(cr3, virt_to_phys(alt_insn_page), insn_ram); + /* Trap, let hypervisor emulate at alt_insn_page */ + asm volatile("call *%0": : "r"(insn_ram+1)); + + outregs = save; +} + +static void advance_rip_by_3_and_note_exception(struct ex_regs *regs) +{ + ++exceptions; + regs->rip += 3; +} + +static void test_mmx_movq_mf(uint64_t *mem, uint8_t *insn_page, + uint8_t *alt_insn_page, void *insn_ram) +{ + uint16_t fcw = 0; /* all exceptions unmasked */ + /* movq %mm0, (%rax) */ + void *stack = alloc_page(); + + write_cr0(read_cr0() & ~6); /* TS, EM */ + exceptions = 0; + handle_exception(MF_VECTOR, advance_rip_by_3_and_note_exception); + asm volatile("fninit; fldcw %0" : : "m"(fcw)); + asm volatile("fldz; fldz; fdivp"); /* generate exception */ + + MK_INSN(mmx_movq_mf, "movq %mm0, (%rax) \n\t"); + inregs = (struct regs){ .rsp=(u64)stack+1024 }; + trap_emulator(mem, alt_insn_page, &insn_mmx_movq_mf); + /* exit MMX mode */ + asm volatile("fnclex; emms"); + report("movq mmx generates #MF", exceptions == 1); + handle_exception(MF_VECTOR, 0); +} + +static void test_movabs(uint64_t *mem, uint8_t *insn_page, + uint8_t *alt_insn_page, void *insn_ram) +{ + /* mov $0x9090909090909090, %rcx */ + MK_INSN(movabs, "mov $0x9090909090909090, %rcx\n\t"); + inregs = (struct regs){ 0 }; + trap_emulator(mem, alt_insn_page, &insn_movabs); + report("64-bit mov imm2", outregs.rcx == 0x9090909090909090); +} + +static void test_crosspage_mmio(volatile uint8_t *mem) +{ + volatile uint16_t w, *pw; + + pw = (volatile uint16_t *)&mem[4095]; + mem[4095] = 0x99; + mem[4096] = 0x77; + asm volatile("mov %1, %0" : "=r"(w) : "m"(*pw) : "memory"); + report("cross-page mmio read", w == 0x7799); + asm volatile("mov %1, %0" : "=m"(*pw) : "r"((uint16_t)0x88aa)); + report("cross-page mmio write", mem[4095] == 0xaa && mem[4096] == 0x88); +} + +static void test_string_io_mmio(volatile uint8_t *mem) +{ + /* Cross MMIO pages.*/ + volatile uint8_t *mmio = mem + 4032; + + asm volatile("outw %%ax, %%dx \n\t" : : "a"(0x9999), "d"(TESTDEV_IO_PORT)); + + asm volatile ("cld; rep insb" : : "d" (TESTDEV_IO_PORT), "D" (mmio), "c" (1024)); + + report("string_io_mmio", mmio[1023] == 0x99); +} + +static void test_lgdt_lidt(volatile uint8_t *mem) +{ + struct descriptor_table_ptr orig, fresh = {}; + + sgdt(&orig); + *(struct descriptor_table_ptr *)mem = (struct descriptor_table_ptr) { + .limit = 0xf234, + .base = 0x12345678abcd, + }; + cli(); + asm volatile("lgdt %0" : : "m"(*(struct descriptor_table_ptr *)mem)); + sgdt(&fresh); + lgdt(&orig); + sti(); + report("lgdt (long address)", orig.limit == fresh.limit && orig.base == fresh.base); + + sidt(&orig); + *(struct descriptor_table_ptr *)mem = (struct descriptor_table_ptr) { + .limit = 0x432f, + .base = 0xdbca87654321, + }; + cli(); + asm volatile("lidt %0" : : "m"(*(struct descriptor_table_ptr *)mem)); + sidt(&fresh); + lidt(&orig); + sti(); + report("lidt (long address)", orig.limit == fresh.limit && orig.base == fresh.base); +} + +static void ss_bad_rpl(struct ex_regs *regs) +{ + extern char ss_bad_rpl_cont; + + ++exceptions; + regs->rip = (ulong)&ss_bad_rpl_cont; +} + +static void test_sreg(volatile uint16_t *mem) +{ + u16 ss = read_ss(); + + // check for null segment load + *mem = 0; + asm volatile("mov %0, %%ss" : : "m"(*mem)); + report("mov null, %ss", read_ss() == 0); + + // check for exception when ss.rpl != cpl on null segment load + exceptions = 0; + handle_exception(GP_VECTOR, ss_bad_rpl); + *mem = 3; + asm volatile("mov %0, %%ss; ss_bad_rpl_cont:" : : "m"(*mem)); + report("mov null, %ss (with ss.rpl != cpl)", exceptions == 1 && read_ss() == 0); + handle_exception(GP_VECTOR, 0); + write_ss(ss); +} + +static void test_lldt(volatile uint16_t *mem) +{ + u64 gdt[] = { 0, 0x0000f82000000ffffull /* ldt descriptor */ }; + struct descriptor_table_ptr gdt_ptr = { .limit = 0xffff, .base = (ulong)&gdt }; + struct descriptor_table_ptr orig_gdt; + + cli(); + sgdt(&orig_gdt); + lgdt(&gdt_ptr); + *mem = 0x8; + asm volatile("lldt %0" : : "m"(*mem)); + lgdt(&orig_gdt); + sti(); + report("lldt", sldt() == *mem); +} + +static void test_ltr(volatile uint16_t *mem) +{ + struct descriptor_table_ptr gdt_ptr; + uint64_t *gdt, *trp; + uint16_t tr = str(); + uint64_t busy_mask = (uint64_t)1 << 41; + + sgdt(&gdt_ptr); + gdt = (uint64_t *)gdt_ptr.base; + trp = &gdt[tr >> 3]; + *trp &= ~busy_mask; + *mem = tr; + asm volatile("ltr %0" : : "m"(*mem) : "memory"); + report("ltr", str() == tr && (*trp & busy_mask)); +} + +static void test_simplealu(u32 *mem) +{ + *mem = 0x1234; + asm("or %1, %0" : "+m"(*mem) : "r"(0x8001)); + report("or", *mem == 0x9235); + asm("add %1, %0" : "+m"(*mem) : "r"(2)); + report("add", *mem == 0x9237); + asm("xor %1, %0" : "+m"(*mem) : "r"(0x1111)); + report("xor", *mem == 0x8326); + asm("sub %1, %0" : "+m"(*mem) : "r"(0x26)); + report("sub", *mem == 0x8300); + asm("clc; adc %1, %0" : "+m"(*mem) : "r"(0x100)); + report("adc(0)", *mem == 0x8400); + asm("stc; adc %1, %0" : "+m"(*mem) : "r"(0x100)); + report("adc(0)", *mem == 0x8501); + asm("clc; sbb %1, %0" : "+m"(*mem) : "r"(0)); + report("sbb(0)", *mem == 0x8501); + asm("stc; sbb %1, %0" : "+m"(*mem) : "r"(0)); + report("sbb(1)", *mem == 0x8500); + asm("and %1, %0" : "+m"(*mem) : "r"(0xfe77)); + report("and", *mem == 0x8400); + asm("test %1, %0" : "+m"(*mem) : "r"(0xf000)); + report("test", *mem == 0x8400); +} + +int main() +{ + void *mem; + void *insn_page, *alt_insn_page; + void *insn_ram; + unsigned long t1, t2; + + setup_vm(); + setup_idt(); + mem = alloc_vpages(2); + install_page((void *)read_cr3(), IORAM_BASE_PHYS, mem); + // install the page twice to test cross-page mmio + install_page((void *)read_cr3(), IORAM_BASE_PHYS, mem + 4096); + insn_page = alloc_page(); + alt_insn_page = alloc_page(); + insn_ram = vmap(virt_to_phys(insn_page), 4096); + + // test mov reg, r/m and mov r/m, reg + t1 = 0x123456789abcdef; + asm volatile("mov %[t1], (%[mem]) \n\t" + "mov (%[mem]), %[t2]" + : [t2]"=r"(t2) + : [t1]"r"(t1), [mem]"r"(mem) + : "memory"); + report("mov reg, r/m (1)", t2 == 0x123456789abcdef); + + test_simplealu(mem); + test_cmps(mem); + test_scas(mem); + + test_push(mem); + test_pop(mem); + + test_xchg(mem); + test_xadd(mem); + + test_cr8(); + + test_smsw(); + test_lmsw(); + test_ljmp(mem); + test_stringio(); + test_incdecnotneg(mem); + test_btc(mem); + test_bsfbsr(mem); + test_imul(mem); + test_muldiv(mem); + test_sse(mem); + test_mmx(mem); + test_rip_relative(mem, insn_ram); + test_shld_shrd(mem); + //test_lgdt_lidt(mem); + test_sreg(mem); + test_lldt(mem); + test_ltr(mem); + + test_mmx_movq_mf(mem, insn_page, alt_insn_page, insn_ram); + test_movabs(mem, insn_page, alt_insn_page, insn_ram); + + test_crosspage_mmio(mem); + + test_string_io_mmio(mem); + + printf("\nSUMMARY: %d tests, %d failures\n", tests, fails); + return fails ? 1 : 0; +} diff --git a/kvm-unittest/x86/eventinj.c b/kvm-unittest/x86/eventinj.c new file mode 100644 index 0000000..3d36b37 --- /dev/null +++ b/kvm-unittest/x86/eventinj.c @@ -0,0 +1,422 @@ +#include "libcflat.h" +#include "processor.h" +#include "vm.h" +#include "desc.h" +#include "isr.h" +#include "apic.h" +#include "apic-defs.h" + +#ifdef __x86_64__ +# define R "r" +#else +# define R "e" +#endif + +static int g_fail; +static int g_tests; + +static inline void io_delay(void) +{ +} + +static inline void outl(int addr, int val) +{ + asm volatile ("outl %1, %w0" : : "d" (addr), "a" (val)); +} + +static void report(const char *msg, int pass) +{ + ++g_tests; + printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL")); + if (!pass) + ++g_fail; +} + +void apic_self_ipi(u8 v) +{ + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | + APIC_INT_ASSERT | v, 0); +} + +void apic_self_nmi(void) +{ + apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0); +} + +static void eoi(void) +{ + apic_write(APIC_EOI, 0); +} + +#define flush_phys_addr(__s) outl(0xe4, __s) +#define flush_stack() do { \ + int __l; \ + flush_phys_addr(virt_to_phys(&__l)); \ + } while (0) + +extern char isr_iret_ip[]; + +static void flush_idt_page() +{ + struct descriptor_table_ptr ptr; + sidt(&ptr); + flush_phys_addr(virt_to_phys((void*)ptr.base)); +} + +static volatile unsigned int test_divider; +static volatile int test_count; + +#ifndef __x86_64__ +ulong stack_phys; +void *stack_va; + +static void pf_tss(void) +{ +start: + printf("PF running\n"); + install_pte(phys_to_virt(read_cr3()), 1, stack_va, + stack_phys | PTE_PRESENT | PTE_WRITE, 0); + invlpg(stack_va); + asm volatile ("iret"); + goto start; +} + +static void of_isr(struct ex_regs *r) +{ + printf("OF isr running\n"); + test_count++; +} + +static void np_isr(struct ex_regs *r) +{ + printf("NP isr running %x err=%x\n", r->rip, r->error_code); + set_idt_sel(33, read_cs()); + test_count++; +} +#endif + +static void de_isr(struct ex_regs *r) +{ + printf("DE isr running divider is %d\n", test_divider); + test_divider = 10; +} + +static void bp_isr(struct ex_regs *r) +{ + printf("BP isr running\n"); + test_count++; +} + +static void nested_nmi_isr(struct ex_regs *r) +{ + printf("Nested NMI isr running rip=%x\n", r->rip); + + if (r->rip != (ulong)&isr_iret_ip) + test_count++; +} +static void nmi_isr(struct ex_regs *r) +{ + printf("NMI isr running %x\n", &isr_iret_ip); + test_count++; + handle_exception(2, nested_nmi_isr); + printf("Sending nested NMI to self\n"); + apic_self_nmi(); + io_delay(); + printf("After nested NMI to self\n"); +} + +unsigned long after_iret_addr; + +static void nested_nmi_iret_isr(struct ex_regs *r) +{ + printf("Nested NMI isr running rip=%x\n", r->rip); + + if (r->rip == after_iret_addr) + test_count++; +} +static void nmi_iret_isr(struct ex_regs *r) +{ + unsigned long *s = alloc_page(); + test_count++; + printf("NMI isr running %p stack %p\n", &&after_iret, s); + handle_exception(2, nested_nmi_iret_isr); + printf("Sending nested NMI to self\n"); + apic_self_nmi(); + printf("After nested NMI to self\n"); + s[4] = read_ss(); + s[3] = 0; /* rsp */ + s[2] = read_rflags(); + s[1] = read_cs(); + s[0] = after_iret_addr = (unsigned long)&&after_iret; + asm ("mov %%rsp, %0\n\t" + "mov %1, %%rsp\n\t" + "outl %2, $0xe4\n\t" /* flush stack page */ +#ifdef __x86_64__ + "iretq\n\t" +#else + "iretl\n\t" +#endif + : "=m"(s[3]) : "rm"(&s[0]), "a"((unsigned int)virt_to_phys(s)) : "memory"); +after_iret: + printf("After iret\n"); +} + +static void tirq0(isr_regs_t *r) +{ + printf("irq0 running\n"); + if (test_count != 0) + test_count++; + eoi(); +} + +static void tirq1(isr_regs_t *r) +{ + printf("irq1 running\n"); + test_count++; + eoi(); +} + +ulong saved_stack; + +#define switch_stack(S) do { \ + asm volatile ("mov %%" R "sp, %0":"=r"(saved_stack)); \ + asm volatile ("mov %0, %%" R "sp"::"r"(S)); \ + } while(0) + +#define restore_stack() do { \ + asm volatile ("mov %0, %%" R "sp"::"r"(saved_stack)); \ + } while(0) + +int main() +{ + unsigned int res; + ulong *pt, *cr3, i; + + setup_vm(); + setup_idt(); + setup_gdt(); + setup_tss32(); + + handle_irq(32, tirq0); + handle_irq(33, tirq1); + + /* generate HW exception that will fault on IDT and stack */ + handle_exception(0, de_isr); + printf("Try to divide by 0\n"); + flush_idt_page(); + flush_stack(); + asm volatile ("divl %3": "=a"(res) + : "d"(0), "a"(1500), "m"(test_divider)); + printf("Result is %d\n", res); + report("DE exception", res == 150); + + /* generate soft exception (BP) that will fault on IDT and stack */ + test_count = 0; + handle_exception(3, bp_isr); + printf("Try int 3\n"); + flush_idt_page(); + flush_stack(); + asm volatile ("int $3"); + printf("After int 3\n"); + report("BP exception", test_count == 1); + +#ifndef __x86_64__ + /* generate soft exception (OF) that will fault on IDT */ + test_count = 0; + handle_exception(4, of_isr); + flush_idt_page(); + printf("Try into\n"); + asm volatile ("addb $127, %b0\ninto"::"a"(127)); + printf("After into\n"); + report("OF exception", test_count == 1); + + /* generate soft exception (OF) using two bit instruction that will + fault on IDT */ + test_count = 0; + handle_exception(4, of_isr); + flush_idt_page(); + printf("Try into\n"); + asm volatile ("addb $127, %b0\naddr16 into"::"a"(127)); + printf("After into\n"); + report("2 byte OF exception", test_count == 1); +#endif + + /* generate HW interrupt that will fault on IDT */ + test_count = 0; + flush_idt_page(); + printf("Sending vec 33 to self\n"); + irq_enable(); + apic_self_ipi(33); + io_delay(); + irq_disable(); + printf("After vec 33 to self\n"); + report("vec 33", test_count == 1); + + /* generate soft interrupt that will fault on IDT and stack */ + test_count = 0; + flush_idt_page(); + printf("Try int $33\n"); + flush_stack(); + asm volatile ("int $33"); + printf("After int $33\n"); + report("int $33", test_count == 1); + + /* Inject two HW interrupt than open iterrupt windows. Both interrupt + will fault on IDT access */ + test_count = 0; + flush_idt_page(); + printf("Sending vec 32 and 33 to self\n"); + apic_self_ipi(32); + apic_self_ipi(33); + io_delay(); + irq_enable(); + asm volatile("nop"); + irq_disable(); + printf("After vec 32 and 33 to self\n"); + report("vec 32/33", test_count == 2); + + + /* Inject HW interrupt, do sti and than (while in irq shadow) inject + soft interrupt. Fault during soft interrupt. Soft interrup shoud be + handled before HW interrupt */ + test_count = 0; + flush_idt_page(); + printf("Sending vec 32 and int $33\n"); + apic_self_ipi(32); + flush_stack(); + io_delay(); + irq_enable(); + asm volatile ("int $33"); + irq_disable(); + printf("After vec 32 and int $33\n"); + report("vec 32/int $33", test_count == 2); + + /* test that TPR is honored */ + test_count = 0; + handle_irq(62, tirq1); + flush_idt_page(); + printf("Sending vec 33 and 62 and mask one with TPR\n"); + apic_write(APIC_TASKPRI, 0xf << 4); + irq_enable(); + apic_self_ipi(32); + apic_self_ipi(62); + io_delay(); + apic_write(APIC_TASKPRI, 0x2 << 4); + printf("After 33/62 TPR test\n"); + report("TPR", test_count == 1); + apic_write(APIC_TASKPRI, 0x0); + while(test_count != 2); /* wait for second irq */ + irq_disable(); + +#ifndef __x86_64__ + /* test fault durint NP delivery */ + printf("Before NP test\n"); + test_count = 0; + handle_exception(11, np_isr); + set_idt_sel(33, NP_SEL); + flush_idt_page(); + flush_stack(); + asm volatile ("int $33"); + printf("After int33\n"); + report("NP exception", test_count == 2); +#endif + + /* generate NMI that will fault on IDT */ + test_count = 0; + handle_exception(2, nmi_isr); + flush_idt_page(); + printf("Sending NMI to self\n"); + apic_self_nmi(); + printf("After NMI to self\n"); + /* this is needed on VMX without NMI window notification. + Interrupt windows is used instead, so let pending NMI + to be injected */ + irq_enable(); + asm volatile ("nop"); + irq_disable(); + report("NMI", test_count == 2); + + /* generate NMI that will fault on IRET */ + printf("Before NMI IRET test\n"); + test_count = 0; + handle_exception(2, nmi_iret_isr); + printf("Sending NMI to self\n"); + apic_self_nmi(); + /* this is needed on VMX without NMI window notification. + Interrupt windows is used instead, so let pending NMI + to be injected */ + irq_enable(); + asm volatile ("nop"); + irq_disable(); + printf("After NMI to self\n"); + report("NMI", test_count == 2); +#ifndef __x86_64__ + stack_phys = (ulong)virt_to_phys(alloc_page()); + stack_va = alloc_vpage(); + + /* Generate DE and PF exceptions serially */ + test_divider = 0; + set_intr_task_gate(14, pf_tss); + handle_exception(0, de_isr); + printf("Try to divide by 0\n"); + /* install read only pte */ + install_pte(phys_to_virt(read_cr3()), 1, stack_va, + stack_phys | PTE_PRESENT, 0); + invlpg(stack_va); + flush_phys_addr(stack_phys); + switch_stack(stack_va + 4095); + flush_idt_page(); + asm volatile ("divl %3": "=a"(res) + : "d"(0), "a"(1500), "m"(test_divider)); + restore_stack(); + printf("Result is %d\n", res); + report("DE PF exceptions", res == 150); + + /* Generate NP and PF exceptions serially */ + printf("Before NP test\n"); + test_count = 0; + set_intr_task_gate(14, pf_tss); + handle_exception(11, np_isr); + set_idt_sel(33, NP_SEL); + /* install read only pte */ + install_pte(phys_to_virt(read_cr3()), 1, stack_va, + stack_phys | PTE_PRESENT, 0); + invlpg(stack_va); + flush_idt_page(); + flush_phys_addr(stack_phys); + switch_stack(stack_va + 4095); + asm volatile ("int $33"); + restore_stack(); + printf("After int33\n"); + report("NP PF exceptions", test_count == 2); +#endif + + pt = alloc_page(); + cr3 = (void*)read_cr3(); + memset(pt, 0, 4096); + /* use shadowed stack during interrupt delivery */ + for (i = 0; i < 4096/sizeof(ulong); i++) { + if (!cr3[i]) { + cr3[i] = virt_to_phys(pt) | PTE_PRESENT | PTE_WRITE; + pt[0] = virt_to_phys(pt) | PTE_PRESENT | PTE_WRITE; +#ifndef __x86_64__ + ((ulong*)(i<<22))[1] = 0; +#else + ((ulong*)(i<<39))[1] = 0; +#endif + write_cr3(virt_to_phys(cr3)); + break; + } + } + test_count = 0; + printf("Try int 33 with shadowed stack\n"); + switch_stack(((char*)pt) + 4095); + asm volatile("int $33"); + restore_stack(); + printf("After int 33 with shadowed stack\n"); + report("int 33 with shadowed stack", test_count == 1); + + printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail); + + return g_fail != 0; +} diff --git a/kvm-unittest/x86/hypercall.c b/kvm-unittest/x86/hypercall.c new file mode 100644 index 0000000..95120a2 --- /dev/null +++ b/kvm-unittest/x86/hypercall.c @@ -0,0 +1,31 @@ +#include "libcflat.h" + +#define KVM_HYPERCALL_INTEL ".byte 0x0f,0x01,0xc1" +#define KVM_HYPERCALL_AMD ".byte 0x0f,0x01,0xd9" + +static inline long kvm_hypercall0_intel(unsigned int nr) +{ + long ret; + asm volatile(KVM_HYPERCALL_INTEL + : "=a"(ret) + : "a"(nr)); + return ret; +} + +static inline long kvm_hypercall0_amd(unsigned int nr) +{ + long ret; + asm volatile(KVM_HYPERCALL_AMD + : "=a"(ret) + : "a"(nr)); + return ret; +} + +int main(int ac, char **av) +{ + kvm_hypercall0_intel(-1u); + printf("Hypercall via VMCALL: OK\n"); + kvm_hypercall0_amd(-1u); + printf("Hypercall via VMMCALL: OK\n"); + return 0; +} diff --git a/kvm-unittest/x86/idt_test.c b/kvm-unittest/x86/idt_test.c new file mode 100644 index 0000000..2d2e0c2 --- /dev/null +++ b/kvm-unittest/x86/idt_test.c @@ -0,0 +1,49 @@ +#include "libcflat.h" +#include "desc.h" + +int test_ud2(void) +{ + asm volatile(ASM_TRY("1f") + "ud2 \n\t" + "1:" :); + return exception_vector(); +} + +int test_gp(void) +{ + unsigned long tmp; + + asm volatile("mov $0xffffffff, %0 \n\t" + ASM_TRY("1f") + "mov %0, %%cr4\n\t" + "1:" + : "=a"(tmp)); + return exception_vector(); +} + +static int nr_fail, nr_test; + +static void report(int cond, const char *name) +{ + ++nr_test; + if (!cond) { + ++nr_fail; + printf("%s: FAIL\n", name); + } else { + printf("%s: PASS\n", name); + } +} + +int main(void) +{ + int r; + + printf("Starting IDT test\n"); + setup_idt(); + r = test_gp(); + report(r == GP_VECTOR, "Testing #GP"); + r = test_ud2(); + report(r == UD_VECTOR, "Testing #UD"); + printf("%d failures of %d tests\n", nr_fail, nr_test); + return !nr_fail ? 0 : 1; +} diff --git a/kvm-unittest/x86/init.c b/kvm-unittest/x86/init.c new file mode 100644 index 0000000..717511e --- /dev/null +++ b/kvm-unittest/x86/init.c @@ -0,0 +1,129 @@ +#include "libcflat.h" +#include "apic.h" +#include "io.h" + +#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */ +#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */ +#define KBD_CCMD_RESET 0xFE /* CPU reset */ + +static inline void kbd_cmd(u8 val) +{ + while (inb(0x64) & 2); + outb(val, 0x64); +} + +static inline u8 kbd_in(void) +{ + kbd_cmd(KBD_CCMD_READ_OUTPORT); + while (inb(0x64) & 2); + return inb(0x60); +} + +static inline void kbd_out(u8 val) +{ + kbd_cmd(KBD_CCMD_WRITE_OUTPORT); + while (inb(0x64) & 2); + outb(val, 0x60); +} + +static inline void rtc_out(u8 reg, u8 val) +{ + outb(reg, 0x70); + outb(val, 0x71); +} + +extern char resume_start, resume_end; + +#define state (*(volatile int *)0x2000) +#define bad (*(volatile int *)0x2004) +#define resumed (*(volatile int *)0x2008) + +int main(int argc, char **argv) +{ + volatile u16 *resume_vector_ptr = (u16 *)0x467L; + char *addr, *resume_vec = (void*)0x1000; + + /* resume execution by indirect jump via 40h:0067h */ + rtc_out(0x0f, 0x0a); + resume_vector_ptr[0] = ((u32)(ulong)resume_vec); + resume_vector_ptr[1] = 0; + + for (addr = &resume_start; addr < &resume_end; addr++) + *resume_vec++ = *addr; + + if (state != 0) { + /* + * Strictly speaking this is a firmware problem, but let's check + * for it as well... + */ + if (resumed != 1) { + printf("Uh, resume vector visited %d times?\n", resumed); + bad |= 2; + } + /* + * Port 92 bit 0 is cleared on system reset. On a soft reset it + * is left to 1. Use this to distinguish INIT from hard reset. + */ + if (resumed != 0 && (inb(0x92) & 1) == 0) { + printf("Uh, hard reset!\n"); + bad |= 1; + } + } + + resumed = 0; + + switch (state++) { + case 0: + printf("testing port 92 init... "); + outb(inb(0x92) & ~1, 0x92); + outb(inb(0x92) | 1, 0x92); + break; + + case 1: + printf("testing kbd controller reset... "); + kbd_cmd(KBD_CCMD_RESET); + break; + + case 2: + printf("testing kbd controller init... "); + kbd_out(kbd_in() & ~1); + break; + + case 3: + printf("testing 0xcf9h init... "); + outb(0, 0xcf9); + outb(4, 0xcf9); + break; + + case 4: + printf("testing init to BSP... "); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL + | APIC_DM_INIT, 0); + break; + + case 5: + exit(bad); + } + + /* The resume code will get us back to main. */ + asm("cli; hlt"); +} + +asm ( + ".global resume_start\n" + ".global resume_end\n" + ".code16\n" + "resume_start:\n" + "incb %cs:0x2008\n" // resumed++; + "mov $0x0f, %al\n" // rtc_out(0x0f, 0x00); + "out %al, $0x70\n" + "mov $0x00, %al\n" + "out %al, $0x71\n" + "jmp $0xffff, $0x0000\n" // BIOS reset + "resume_end:\n" +#ifdef __i386__ + ".code32\n" +#else + ".code64\n" +#endif + ); diff --git a/kvm-unittest/x86/kvmclock.c b/kvm-unittest/x86/kvmclock.c new file mode 100644 index 0000000..5b831c5 --- /dev/null +++ b/kvm-unittest/x86/kvmclock.c @@ -0,0 +1,279 @@ +#include "libcflat.h" +#include "smp.h" +#include "atomic.h" +#include "processor.h" +#include "kvmclock.h" + +#define unlikely(x) __builtin_expect(!!(x), 0) +#define likely(x) __builtin_expect(!!(x), 1) + + +struct pvclock_vcpu_time_info __attribute__((aligned(4))) hv_clock[MAX_CPU]; +struct pvclock_wall_clock wall_clock; +static unsigned char valid_flags = 0; +static atomic64_t last_value = ATOMIC64_INIT(0); + +/* + * Scale a 64-bit delta by scaling and multiplying by a 32-bit fraction, + * yielding a 64-bit result. + */ +static inline u64 scale_delta(u64 delta, u32 mul_frac, int shift) +{ + u64 product; +#ifdef __i386__ + u32 tmp1, tmp2; +#endif + + if (shift < 0) + delta >>= -shift; + else + delta <<= shift; + +#ifdef __i386__ + __asm__ ( + "mul %5 ; " + "mov %4,%%eax ; " + "mov %%edx,%4 ; " + "mul %5 ; " + "xor %5,%5 ; " + "add %4,%%eax ; " + "adc %5,%%edx ; " + : "=A" (product), "=r" (tmp1), "=r" (tmp2) + : "a" ((u32)delta), "1" ((u32)(delta >> 32)), "2" (mul_frac) ); +#elif defined(__x86_64__) + __asm__ ( + "mul %%rdx ; shrd $32,%%rdx,%%rax" + : "=a" (product) : "0" (delta), "d" ((u64)mul_frac) ); +#else +#error implement me! +#endif + + return product; +} + +#ifdef __i386__ +# define do_div(n,base) ({ \ + u32 __base = (base); \ + u32 __rem; \ + __rem = ((u64)(n)) % __base; \ + (n) = ((u64)(n)) / __base; \ + __rem; \ + }) +#else +u32 __attribute__((weak)) __div64_32(u64 *n, u32 base) +{ + u64 rem = *n; + u64 b = base; + u64 res, d = 1; + u32 high = rem >> 32; + + /* Reduce the thing a bit first */ + res = 0; + if (high >= base) { + high /= base; + res = (u64) high << 32; + rem -= (u64) (high*base) << 32; + } + + while ((s64)b > 0 && b < rem) { + b = b+b; + d = d+d; + } + + do { + if (rem >= b) { + rem -= b; + res += d; + } + b >>= 1; + d >>= 1; + } while (d); + + *n = res; + return rem; +} + +# define do_div(n,base) ({ \ + u32 __base = (base); \ + u32 __rem; \ + (void)(((typeof((n)) *)0) == ((u64 *)0)); \ + if (likely(((n) >> 32) == 0)) { \ + __rem = (u32)(n) % __base; \ + (n) = (u32)(n) / __base; \ + } else \ + __rem = __div64_32(&(n), __base); \ + __rem; \ + }) +#endif + +/** + * set_normalized_timespec - set timespec sec and nsec parts and normalize + * + * @ts: pointer to timespec variable to be set + * @sec: seconds to set + * @nsec: nanoseconds to set + * + * Set seconds and nanoseconds field of a timespec variable and + * normalize to the timespec storage format + * + * Note: The tv_nsec part is always in the range of + * 0 <= tv_nsec < NSEC_PER_SEC + * For negative values only the tv_sec field is negative ! + */ +void set_normalized_timespec(struct timespec *ts, long sec, s64 nsec) +{ + while (nsec >= NSEC_PER_SEC) { + /* + * The following asm() prevents the compiler from + * optimising this loop into a modulo operation. See + * also __iter_div_u64_rem() in include/linux/time.h + */ + asm("" : "+rm"(nsec)); + nsec -= NSEC_PER_SEC; + ++sec; + } + while (nsec < 0) { + asm("" : "+rm"(nsec)); + nsec += NSEC_PER_SEC; + --sec; + } + ts->tv_sec = sec; + ts->tv_nsec = nsec; +} + +static u64 pvclock_get_nsec_offset(struct pvclock_shadow_time *shadow) +{ + u64 delta = rdtsc() - shadow->tsc_timestamp; + return scale_delta(delta, shadow->tsc_to_nsec_mul, shadow->tsc_shift); +} + +/* + * Reads a consistent set of time-base values from hypervisor, + * into a shadow data area. + */ +static unsigned pvclock_get_time_values(struct pvclock_shadow_time *dst, + struct pvclock_vcpu_time_info *src) +{ + do { + dst->version = src->version; + rmb(); /* fetch version before data */ + dst->tsc_timestamp = src->tsc_timestamp; + dst->system_timestamp = src->system_time; + dst->tsc_to_nsec_mul = src->tsc_to_system_mul; + dst->tsc_shift = src->tsc_shift; + dst->flags = src->flags; + rmb(); /* test version after fetching data */ + } while ((src->version & 1) || (dst->version != src->version)); + + return dst->version; +} + +cycle_t pvclock_clocksource_read(struct pvclock_vcpu_time_info *src) +{ + struct pvclock_shadow_time shadow; + unsigned version; + cycle_t ret, offset; + u64 last; + + do { + version = pvclock_get_time_values(&shadow, src); + mb(); + offset = pvclock_get_nsec_offset(&shadow); + ret = shadow.system_timestamp + offset; + mb(); + } while (version != src->version); + + if ((valid_flags & PVCLOCK_RAW_CYCLE_BIT) || + ((valid_flags & PVCLOCK_TSC_STABLE_BIT) && + (shadow.flags & PVCLOCK_TSC_STABLE_BIT))) + return ret; + + /* + * Assumption here is that last_value, a global accumulator, always goes + * forward. If we are less than that, we should not be much smaller. + * We assume there is an error marging we're inside, and then the correction + * does not sacrifice accuracy. + * + * For reads: global may have changed between test and return, + * but this means someone else updated poked the clock at a later time. + * We just need to make sure we are not seeing a backwards event. + * + * For updates: last_value = ret is not enough, since two vcpus could be + * updating at the same time, and one of them could be slightly behind, + * making the assumption that last_value always go forward fail to hold. + */ + last = atomic64_read(&last_value); + do { + if (ret < last) + return last; + last = atomic64_cmpxchg(&last_value, last, ret); + } while (unlikely(last != ret)); + + return ret; +} + +cycle_t kvm_clock_read() +{ + struct pvclock_vcpu_time_info *src; + cycle_t ret; + int index = smp_id(); + + src = &hv_clock[index]; + ret = pvclock_clocksource_read(src); + return ret; +} + +void kvm_clock_init(void *data) +{ + int index = smp_id(); + struct pvclock_vcpu_time_info *hvc = &hv_clock[index]; + + printf("kvm-clock: cpu %d, msr 0x:%lx \n", index, hvc); + wrmsr(MSR_KVM_SYSTEM_TIME, (unsigned long)hvc | 1); +} + +void kvm_clock_clear(void *data) +{ + wrmsr(MSR_KVM_SYSTEM_TIME, 0LL); +} + +void pvclock_read_wallclock(struct pvclock_wall_clock *wall_clock, + struct pvclock_vcpu_time_info *vcpu_time, + struct timespec *ts) +{ + u32 version; + u64 delta; + struct timespec now; + + /* get wallclock at system boot */ + do { + version = wall_clock->version; + rmb(); /* fetch version before time */ + now.tv_sec = wall_clock->sec; + now.tv_nsec = wall_clock->nsec; + rmb(); /* fetch time before checking version */ + } while ((wall_clock->version & 1) || (version != wall_clock->version)); + + delta = pvclock_clocksource_read(vcpu_time); /* time since system boot */ + delta += now.tv_sec * (u64)NSEC_PER_SEC + now.tv_nsec; + + now.tv_nsec = do_div(delta, NSEC_PER_SEC); + now.tv_sec = delta; + + set_normalized_timespec(ts, now.tv_sec, now.tv_nsec); +} + +void kvm_get_wallclock(struct timespec *ts) +{ + struct pvclock_vcpu_time_info *vcpu_time; + int index = smp_id(); + + wrmsr(MSR_KVM_WALL_CLOCK, (unsigned long)&wall_clock); + vcpu_time = &hv_clock[index]; + pvclock_read_wallclock(&wall_clock, vcpu_time, ts); +} + +void pvclock_set_flags(unsigned char flags) +{ + valid_flags = flags; +} diff --git a/kvm-unittest/x86/kvmclock_test.c b/kvm-unittest/x86/kvmclock_test.c new file mode 100644 index 0000000..52a43fb --- /dev/null +++ b/kvm-unittest/x86/kvmclock_test.c @@ -0,0 +1,167 @@ +#include "libcflat.h" +#include "smp.h" +#include "atomic.h" +#include "processor.h" +#include "kvmclock.h" + +#define DEFAULT_TEST_LOOPS 100000000L +#define DEFAULT_THRESHOLD 5L + +struct test_info { + struct spinlock lock; + long loops; /* test loops */ + u64 warps; /* warp count */ + 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 ? */ +}; + +struct test_info ti[4]; + +static int wallclock_test(long sec, long threshold) +{ + long ksec, offset; + struct timespec ts; + + printf("Wallclock test, threshold %ld\n", threshold); + kvm_get_wallclock(&ts); + ksec = ts.tv_sec; + + offset = ksec - sec; + printf("Seconds get from host: %ld\n", sec); + printf("Seconds get from kvmclock: %ld\n", ksec); + printf("Offset: %ld\n", offset); + + if (offset > threshold || offset < -threshold) { + printf("offset too large!\n"); + return 1; + } + + return 0; +} + +static void kvm_clock_test(void *data) +{ + struct test_info *hv_test_info = (struct test_info *)data; + long i, check = hv_test_info->check; + + for (i = 0; i < hv_test_info->loops; i++){ + cycle_t t0, t1; + long long delta; + + if (check == 0) { + kvm_clock_read(); + continue; + } + + spin_lock(&hv_test_info->lock); + t1 = kvm_clock_read(); + t0 = hv_test_info->last; + hv_test_info->last = kvm_clock_read(); + spin_unlock(&hv_test_info->lock); + + delta = t1 - t0; + if (delta < 0) { + spin_lock(&hv_test_info->lock); + ++hv_test_info->warps; + if (delta < hv_test_info->worst){ + hv_test_info->worst = delta; + printf("Worst warp %lld %\n", hv_test_info->worst); + } + spin_unlock(&hv_test_info->lock); + } + if (delta == 0) + ++hv_test_info->stalls; + + if (!((unsigned long)i & 31)) + asm volatile("rep; nop"); + } + + atomic_dec(&hv_test_info->ncpus); +} + +static int cycle_test(int ncpus, long loops, int check, struct test_info *ti) +{ + int i; + unsigned long long begin, end; + + begin = rdtsc(); + + atomic_set(&ti->ncpus, ncpus); + ti->loops = loops; + 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)) + ; + + end = rdtsc(); + + printf("Total vcpus: %d\n", ncpus); + printf("Test loops: %ld\n", ti->loops); + if (check == 1) { + printf("Total warps: %lld\n", ti->warps); + printf("Total stalls: %lld\n", ti->stalls); + printf("Worst warp: %lld\n", ti->worst); + } else + printf("TSC cycles: %lld\n", end - begin); + + return ti->warps ? 1 : 0; +} + +int main(int ac, char **av) +{ + int ncpus; + int nerr = 0, i; + long loops = DEFAULT_TEST_LOOPS; + long sec = 0; + long threshold = DEFAULT_THRESHOLD; + + if (ac > 1) + loops = atol(av[1]); + if (ac > 2) + sec = atol(av[2]); + if (ac > 3) + threshold = atol(av[3]); + + smp_init(); + + ncpus = cpu_count(); + if (ncpus > MAX_CPU) + ncpus = MAX_CPU; + for (i = 0; i < ncpus; ++i) + on_cpu(i, kvm_clock_init, (void *)0); + + if (ac > 2) + nerr += wallclock_test(sec, threshold); + + printf("Check the stability of raw cycle ...\n"); + pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT + | PVCLOCK_RAW_CYCLE_BIT); + if (cycle_test(ncpus, loops, 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, loops, 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, loops, 0, &ti[2]); + + printf("Measure the performance of adjusted cycle ...\n"); + pvclock_set_flags(PVCLOCK_TSC_STABLE_BIT); + cycle_test(ncpus, loops, 0, &ti[3]); + + for (i = 0; i < ncpus; ++i) + on_cpu(i, kvm_clock_clear, (void *)0); + + return nerr > 0 ? 1 : 0; +} diff --git a/kvm-unittest/x86/msr.c b/kvm-unittest/x86/msr.c new file mode 100644 index 0000000..de7573d --- /dev/null +++ b/kvm-unittest/x86/msr.c @@ -0,0 +1,149 @@ +/* msr tests */ + +#include "libcflat.h" +#include "processor.h" +#include "msr.h" + +struct msr_info { + int index; + char *name; + struct tc { + int valid; + unsigned long long value; + unsigned long long expected; + } val_pairs[20]; +}; + + +#define addr_64 0x0000123456789abcULL + +struct msr_info msr_info[] = +{ + { .index = 0x0000001b, .name = "MSR_IA32_APICBASE", + .val_pairs = { + { .valid = 1, .value = 0x0000000056789900, .expected = 0x0000000056789900}, + { .valid = 1, .value = 0x0000000056789D01, .expected = 0x0000000056789D01}, + } + }, + { .index = 0x00000174, .name = "IA32_SYSENTER_CS", + .val_pairs = {{ .valid = 1, .value = 0x1234, .expected = 0x1234}} + }, + { .index = 0x00000175, .name = "MSR_IA32_SYSENTER_ESP", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, + { .index = 0x00000176, .name = "IA32_SYSENTER_EIP", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, + { .index = 0x000001a0, .name = "MSR_IA32_MISC_ENABLE", + // reserved: 1:2, 4:6, 8:10, 13:15, 17, 19:21, 24:33, 35:63 + .val_pairs = {{ .valid = 1, .value = 0x400c51889, .expected = 0x400c51889}} + }, + { .index = 0x00000277, .name = "MSR_IA32_CR_PAT", + .val_pairs = {{ .valid = 1, .value = 0x07070707, .expected = 0x07070707}} + }, + { .index = 0xc0000100, .name = "MSR_FS_BASE", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, + { .index = 0xc0000101, .name = "MSR_GS_BASE", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, + { .index = 0xc0000102, .name = "MSR_KERNEL_GS_BASE", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, +#ifdef __x86_64__ + { .index = 0xc0000080, .name = "MSR_EFER", + .val_pairs = {{ .valid = 1, .value = 0xD00, .expected = 0xD00}} + }, +#endif + { .index = 0xc0000082, .name = "MSR_LSTAR", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, + { .index = 0xc0000083, .name = "MSR_CSTAR", + .val_pairs = {{ .valid = 1, .value = addr_64, .expected = addr_64}} + }, + { .index = 0xc0000084, .name = "MSR_SYSCALL_MASK", + .val_pairs = {{ .valid = 1, .value = 0xffffffff, .expected = 0xffffffff}} + }, + +// MSR_IA32_DEBUGCTLMSR needs svm feature LBRV +// MSR_VM_HSAVE_PA only AMD host +}; + +static int find_msr_info(int msr_index) +{ + int i; + for (i = 0; i < sizeof(msr_info)/sizeof(msr_info[0]) ; i++) { + if (msr_info[i].index == msr_index) { + return i; + } + } + return -1; +} + + +int nr_passed, nr_tests; + +static void report(const char *name, int passed) +{ + ++nr_tests; + if (passed) + ++nr_passed; + printf("%s: %s\n", name, passed ? "PASS" : "FAIL"); +} + +static void test_msr_rw(int msr_index, unsigned long long input, unsigned long long expected) +{ + unsigned long long r = 0; + int index; + 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); + 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, r >> 32, r, expected >> 32, 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 +} + +int main(int ac, char **av) +{ + int i, j; + for (i = 0 ; i < sizeof(msr_info) / sizeof(msr_info[0]); i++) { + for (j = 0; j < sizeof(msr_info[i].val_pairs) / sizeof(msr_info[i].val_pairs[0]); j++) { + if (msr_info[i].val_pairs[j].valid) { + test_msr_rw(msr_info[i].index, msr_info[i].val_pairs[j].value, msr_info[i].val_pairs[j].expected); + } else { + break; + } + } + } + + test_syscall_lazy_load(); + + printf("%d tests, %d failures\n", nr_tests, nr_tests - nr_passed); + + return nr_passed == nr_tests ? 0 : 1; +} + diff --git a/kvm-unittest/x86/pcid.c b/kvm-unittest/x86/pcid.c new file mode 100644 index 0000000..8bfeba2 --- /dev/null +++ b/kvm-unittest/x86/pcid.c @@ -0,0 +1,186 @@ +/* Basic PCID & INVPCID functionality test */ + +#include "libcflat.h" +#include "processor.h" +#include "desc.h" + +int nr_passed, nr_tests; + +#define X86_FEATURE_PCID (1 << 17) +#define X86_FEATURE_INVPCID (1 << 10) + +#define X86_CR0_PG (1 << 31) +#define X86_CR3_PCID_MASK 0x00000fff +#define X86_CR4_PCIDE (1 << 17) + +#define X86_IA32_EFER 0xc0000080 +#define X86_EFER_LMA (1UL << 8) + +struct invpcid_desc { + unsigned long pcid : 12; + unsigned long rsv : 52; + unsigned long addr : 64; +}; + +static void report(const char *name, int passed) +{ + ++nr_tests; + if (passed) + ++nr_passed; + printf("%s: %s\n", name, passed ? "PASS" : "FAIL"); +} + +int write_cr0_checking(unsigned long val) +{ + asm volatile(ASM_TRY("1f") + "mov %0, %%cr0\n\t" + "1:": : "r" (val)); + return exception_vector(); +} + +int write_cr4_checking(unsigned long val) +{ + asm volatile(ASM_TRY("1f") + "mov %0, %%cr4\n\t" + "1:": : "r" (val)); + return exception_vector(); +} + +int invpcid_checking(unsigned long type, void *desc) +{ + asm volatile (ASM_TRY("1f") + ".byte 0x66,0x0f,0x38,0x82,0x18 \n\t" /* invpcid (%rax), %rbx */ + "1:" : : "a" (desc), "b" (type)); + return exception_vector(); +} + +void test_cpuid_consistency(int pcid_enabled, int invpcid_enabled) +{ + int passed = !(!pcid_enabled && invpcid_enabled); + report("CPUID consistency", passed); +} + +void test_pcid_enabled(void) +{ + int passed = 0; + ulong cr0 = read_cr0(), cr3 = read_cr3(), cr4 = read_cr4(); + + /* try setting CR4.PCIDE, no exception expected */ + if (write_cr4_checking(cr4 | X86_CR4_PCIDE) != 0) + goto report; + + /* try clearing CR0.PG when CR4.PCIDE=1, #GP expected */ + if (write_cr0_checking(cr0 | X86_CR0_PG) != GP_VECTOR) + goto report; + + write_cr4(cr4); + + /* try setting CR4.PCIDE when CR3[11:0] != 0 , #GP expected */ + write_cr3(cr3 | 0x001); + if (write_cr4_checking(cr4 | X86_CR4_PCIDE) != GP_VECTOR) + goto report; + write_cr3(cr3); + + passed = 1; + +report: + report("Test on PCID when enabled", passed); +} + +void test_pcid_disabled(void) +{ + int passed = 0; + ulong cr4 = read_cr4(); + + /* try setting CR4.PCIDE, #GP expected */ + if (write_cr4_checking(cr4 | X86_CR4_PCIDE) != GP_VECTOR) + goto report; + + passed = 1; + +report: + report("Test on PCID when disabled", passed); +} + +void test_invpcid_enabled(void) +{ + int passed = 0; + ulong cr4 = read_cr4(); + struct invpcid_desc desc; + desc.rsv = 0; + + /* try executing invpcid when CR4.PCIDE=0, desc.pcid=0 and type=1 + * no exception expected + */ + desc.pcid = 0; + if (invpcid_checking(1, &desc) != 0) + goto report; + + /* try executing invpcid when CR4.PCIDE=0, desc.pcid=1 and type=1 + * #GP expected + */ + desc.pcid = 1; + if (invpcid_checking(1, &desc) != GP_VECTOR) + goto report; + + if (write_cr4_checking(cr4 | X86_CR4_PCIDE) != 0) + goto report; + + /* try executing invpcid when CR4.PCIDE=1 + * no exception expected + */ + desc.pcid = 10; + if (invpcid_checking(2, &desc) != 0) + goto report; + + passed = 1; + +report: + report("Test on INVPCID when enabled", passed); +} + +void test_invpcid_disabled(void) +{ + int passed = 0; + struct invpcid_desc desc; + + /* try executing invpcid, #UD expected */ + if (invpcid_checking(2, &desc) != UD_VECTOR) + goto report; + + passed = 1; + +report: + report("Test on INVPCID when disabled", passed); +} + +int main(int ac, char **av) +{ + struct cpuid _cpuid; + int pcid_enabled = 0, invpcid_enabled = 0; + + setup_idt(); + + _cpuid = cpuid(1); + if (_cpuid.c & X86_FEATURE_PCID) + pcid_enabled = 1; + _cpuid = cpuid_indexed(7, 0); + if (_cpuid.b & X86_FEATURE_INVPCID) + invpcid_enabled = 1; + + test_cpuid_consistency(pcid_enabled, invpcid_enabled); + + if (pcid_enabled) + test_pcid_enabled(); + else + test_pcid_disabled(); + + if (invpcid_enabled) + test_invpcid_enabled(); + else + test_invpcid_disabled(); + + printf("%d tests, %d failures\n", nr_tests, nr_tests - nr_passed); + + return nr_passed == nr_tests ? 0 : 1; +} diff --git a/kvm-unittest/x86/pmu.c b/kvm-unittest/x86/pmu.c new file mode 100644 index 0000000..42b5a59 --- /dev/null +++ b/kvm-unittest/x86/pmu.c @@ -0,0 +1,414 @@ + +#include "x86/msr.h" +#include "x86/processor.h" +#include "x86/apic-defs.h" +#include "x86/apic.h" +#include "x86/desc.h" +#include "x86/isr.h" +#include "x86/vm.h" + +#include "libcflat.h" +#include + +#define FIXED_CNT_INDEX 32 +#define PC_VECTOR 32 + +#define EVNSEL_EVENT_SHIFT 0 +#define EVNTSEL_UMASK_SHIFT 8 +#define EVNTSEL_USR_SHIFT 16 +#define EVNTSEL_OS_SHIFT 17 +#define EVNTSEL_EDGE_SHIFT 18 +#define EVNTSEL_PC_SHIFT 19 +#define EVNTSEL_INT_SHIFT 20 +#define EVNTSEL_EN_SHIF 22 +#define EVNTSEL_INV_SHIF 23 +#define EVNTSEL_CMASK_SHIFT 24 + +#define EVNTSEL_EN (1 << EVNTSEL_EN_SHIF) +#define EVNTSEL_USR (1 << EVNTSEL_USR_SHIFT) +#define EVNTSEL_OS (1 << EVNTSEL_OS_SHIFT) +#define EVNTSEL_PC (1 << EVNTSEL_PC_SHIFT) +#define EVNTSEL_INT (1 << EVNTSEL_INT_SHIFT) +#define EVNTSEL_INV (1 << EVNTSEL_INV_SHIF) + +#define N 1000000 + +typedef struct { + uint32_t ctr; + uint32_t config; + uint64_t count; + int idx; +} pmu_counter_t; + +union cpuid10_eax { + struct { + unsigned int version_id:8; + unsigned int num_counters:8; + unsigned int bit_width:8; + unsigned int mask_length:8; + } split; + unsigned int full; +} eax; + +union cpuid10_ebx { + struct { + unsigned int no_unhalted_core_cycles:1; + unsigned int no_instructions_retired:1; + unsigned int no_unhalted_reference_cycles:1; + unsigned int no_llc_reference:1; + unsigned int no_llc_misses:1; + unsigned int no_branch_instruction_retired:1; + unsigned int no_branch_misses_retired:1; + } split; + unsigned int full; +} ebx; + +union cpuid10_edx { + struct { + unsigned int num_counters_fixed:5; + unsigned int bit_width_fixed:8; + unsigned int reserved:19; + } split; + unsigned int full; +} edx; + +struct pmu_event { + char *name; + uint32_t unit_sel; + int min; + int max; +} gp_events[] = { + {"core cycles", 0x003c, 1*N, 50*N}, + {"instructions", 0x00c0, 10*N, 10.2*N}, + {"ref cycles", 0x013c, 0.1*N, 30*N}, + {"llc refference", 0x4f2e, 1, 2*N}, + {"llc misses", 0x412e, 1, 1*N}, + {"branches", 0x00c4, 1*N, 1.1*N}, + {"branch misses", 0x00c5, 0, 0.1*N}, +}, fixed_events[] = { + {"fixed 1", MSR_CORE_PERF_FIXED_CTR0, 10*N, 10.2*N}, + {"fixed 2", MSR_CORE_PERF_FIXED_CTR0 + 1, 1*N, 30*N}, + {"fixed 3", MSR_CORE_PERF_FIXED_CTR0 + 2, 0.1*N, 30*N} +}; + +static int num_counters; +static int tests, failures; + +char *buf; + +static inline void loop() +{ + unsigned long tmp, tmp2, tmp3; + + asm volatile("1: mov (%1), %2; add $64, %1; nop; nop; nop; nop; nop; nop; nop; loop 1b" + : "=c"(tmp), "=r"(tmp2), "=r"(tmp3): "0"(N), "1"(buf)); + +} + +volatile uint64_t irq_received; + +static void cnt_overflow(isr_regs_t *regs) +{ + irq_received++; + apic_write(APIC_EOI, 0); +} + +static bool check_irq(void) +{ + int i; + irq_received = 0; + irq_enable(); + for (i = 0; i < 100000 && !irq_received; i++) + asm volatile("pause"); + irq_disable(); + return irq_received; +} + +static bool is_gp(pmu_counter_t *evt) +{ + return evt->ctr < MSR_CORE_PERF_FIXED_CTR0; +} + +static int event_to_global_idx(pmu_counter_t *cnt) +{ + return cnt->ctr - (is_gp(cnt) ? MSR_IA32_PERFCTR0 : + (MSR_CORE_PERF_FIXED_CTR0 - FIXED_CNT_INDEX)); +} + +static struct pmu_event* get_counter_event(pmu_counter_t *cnt) +{ + if (is_gp(cnt)) { + int i; + + for (i = 0; i < sizeof(gp_events)/sizeof(gp_events[0]); i++) + if (gp_events[i].unit_sel == (cnt->config & 0xffff)) + return &gp_events[i]; + } else + return &fixed_events[cnt->ctr - MSR_CORE_PERF_FIXED_CTR0]; + + return (void*)0; +} + +static void global_enable(pmu_counter_t *cnt) +{ + cnt->idx = event_to_global_idx(cnt); + + wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, rdmsr(MSR_CORE_PERF_GLOBAL_CTRL) | + (1ull << cnt->idx)); +} + +static void global_disable(pmu_counter_t *cnt) +{ + wrmsr(MSR_CORE_PERF_GLOBAL_CTRL, rdmsr(MSR_CORE_PERF_GLOBAL_CTRL) & + ~(1ull << cnt->idx)); +} + + +static void start_event(pmu_counter_t *evt) +{ + wrmsr(evt->ctr, evt->count); + if (is_gp(evt)) + wrmsr(MSR_P6_EVNTSEL0 + event_to_global_idx(evt), + evt->config | EVNTSEL_EN); + else { + uint32_t ctrl = rdmsr(MSR_CORE_PERF_FIXED_CTR_CTRL); + int shift = (evt->ctr - MSR_CORE_PERF_FIXED_CTR0) * 4; + uint32_t usrospmi = 0; + + if (evt->config & EVNTSEL_OS) + usrospmi |= (1 << 0); + if (evt->config & EVNTSEL_USR) + usrospmi |= (1 << 1); + if (evt->config & EVNTSEL_INT) + usrospmi |= (1 << 3); // PMI on overflow + ctrl = (ctrl & ~(0xf << shift)) | (usrospmi << shift); + wrmsr(MSR_CORE_PERF_FIXED_CTR_CTRL, ctrl); + } + global_enable(evt); +} + +static void stop_event(pmu_counter_t *evt) +{ + global_disable(evt); + if (is_gp(evt)) + wrmsr(MSR_P6_EVNTSEL0 + event_to_global_idx(evt), + evt->config & ~EVNTSEL_EN); + else { + uint32_t ctrl = rdmsr(MSR_CORE_PERF_FIXED_CTR_CTRL); + int shift = (evt->ctr - MSR_CORE_PERF_FIXED_CTR0) * 4; + wrmsr(MSR_CORE_PERF_FIXED_CTR_CTRL, ctrl & ~(0xf << shift)); + } + evt->count = rdmsr(evt->ctr); +} + +static void measure(pmu_counter_t *evt, int count) +{ + int i; + for (i = 0; i < count; i++) + start_event(&evt[i]); + loop(); + for (i = 0; i < count; i++) + stop_event(&evt[i]); +} + +static void report(const char *name, int n, bool pass) +{ + printf("%s: pmu %s-%d\n", pass ? "PASS" : "FAIL", name, n); + tests += 1; + failures += !pass; +} + +static bool verify_event(uint64_t count, struct pmu_event *e) +{ + // printf("%lld >= %lld <= %lld\n", e->min, count, e->max); + return count >= e->min && count <= e->max; + +} + +static bool verify_counter(pmu_counter_t *cnt) +{ + return verify_event(cnt->count, get_counter_event(cnt)); +} + +static void check_gp_counter(struct pmu_event *evt) +{ + pmu_counter_t cnt = { + .ctr = MSR_IA32_PERFCTR0, + .config = EVNTSEL_OS | EVNTSEL_USR | evt->unit_sel, + }; + int i; + + for (i = 0; i < num_counters; i++, cnt.ctr++) { + cnt.count = 0; + measure(&cnt, 1); + report(evt->name, i, verify_event(cnt.count, evt)); + } +} + +static void check_gp_counters(void) +{ + int i; + + for (i = 0; i < sizeof(gp_events)/sizeof(gp_events[0]); i++) + if (!(ebx.full & (1 << i))) + check_gp_counter(&gp_events[i]); + else + printf("GP event '%s' is disabled\n", + gp_events[i].name); +} + +static void check_fixed_counters(void) +{ + pmu_counter_t cnt = { + .config = EVNTSEL_OS | EVNTSEL_USR, + }; + int i; + + for (i = 0; i < edx.split.num_counters_fixed; i++) { + cnt.count = 0; + cnt.ctr = fixed_events[i].unit_sel; + measure(&cnt, 1); + report("fixed", i, verify_event(cnt.count, &fixed_events[i])); + } +} + +static void check_counters_many(void) +{ + pmu_counter_t cnt[10]; + int i, n; + + for (i = 0, n = 0; n < num_counters; i++) { + if (ebx.full & (1 << i)) + continue; + + cnt[n].count = 0; + cnt[n].ctr = MSR_IA32_PERFCTR0 + n; + cnt[n].config = EVNTSEL_OS | EVNTSEL_USR | gp_events[i].unit_sel; + n++; + } + for (i = 0; i < edx.split.num_counters_fixed; i++) { + cnt[n].count = 0; + cnt[n].ctr = fixed_events[i].unit_sel; + cnt[n].config = EVNTSEL_OS | EVNTSEL_USR; + n++; + } + + measure(cnt, n); + + for (i = 0; i < n; i++) + if (!verify_counter(&cnt[i])) + break; + + report("all counters", 0, i == n); +} + +static void check_counter_overflow(void) +{ + uint64_t count; + int i; + pmu_counter_t cnt = { + .ctr = MSR_IA32_PERFCTR0, + .config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel /* instructions */, + .count = 0, + }; + measure(&cnt, 1); + count = cnt.count; + + /* clear status before test */ + wrmsr(MSR_CORE_PERF_GLOBAL_OVF_CTRL, rdmsr(MSR_CORE_PERF_GLOBAL_STATUS)); + + for (i = 0; i < num_counters + 1; i++, cnt.ctr++) { + uint64_t status; + int idx; + if (i == num_counters) + cnt.ctr = fixed_events[0].unit_sel; + if (i % 2) + cnt.config |= EVNTSEL_INT; + else + cnt.config &= ~EVNTSEL_INT; + idx = event_to_global_idx(&cnt); + cnt.count = 1 - count; + measure(&cnt, 1); + report("overflow", i, cnt.count == 1); + status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS); + report("overflow status", i, status & (1ull << idx)); + wrmsr(MSR_CORE_PERF_GLOBAL_OVF_CTRL, status); + status = rdmsr(MSR_CORE_PERF_GLOBAL_STATUS); + report("overflow status clear", i, !(status & (1ull << idx))); + report("overflow irq", i, check_irq() == (i % 2)); + } +} + +static void check_gp_counter_cmask(void) +{ + pmu_counter_t cnt = { + .ctr = MSR_IA32_PERFCTR0, + .config = EVNTSEL_OS | EVNTSEL_USR | gp_events[1].unit_sel /* instructions */, + .count = 0, + }; + cnt.config |= (0x2 << EVNTSEL_CMASK_SHIFT); + measure(&cnt, 1); + report("cmask", 0, cnt.count < gp_events[1].min); +} + +static void check_rdpmc(void) +{ + uint64_t val = 0x1f3456789ull; + int i; + + for (i = 0; i < num_counters; i++) { + uint64_t x = (val & 0xffffffff) | + ((1ull << (eax.split.bit_width - 32)) - 1) << 32; + wrmsr(MSR_IA32_PERFCTR0 + i, val); + report("rdpmc", i, rdpmc(i) == x); + report("rdpmc fast", i, rdpmc(i | (1<<31)) == (u32)val); + } + for (i = 0; i < edx.split.num_counters_fixed; i++) { + uint64_t x = (val & 0xffffffff) | + ((1ull << (edx.split.bit_width_fixed - 32)) - 1) << 32; + wrmsr(MSR_CORE_PERF_FIXED_CTR0 + i, val); + report("rdpmc fixed", i, rdpmc(i | (1 << 30)) == x); + report("rdpmc fixed fast", i, rdpmc(i | (3<<30)) == (u32)val); + } +} + +int main(int ac, char **av) +{ + struct cpuid id = cpuid(10); + + setup_vm(); + setup_idt(); + handle_irq(PC_VECTOR, cnt_overflow); + buf = vmalloc(N*64); + + eax.full = id.a; + ebx.full = id.b; + edx.full = id.d; + + if (!eax.split.version_id) { + printf("No pmu is detected!\n"); + return 1; + } + printf("PMU version: %d\n", eax.split.version_id); + printf("GP counters: %d\n", eax.split.num_counters); + printf("GP counter width: %d\n", eax.split.bit_width); + printf("Mask length: %d\n", eax.split.mask_length); + printf("Fixed counters: %d\n", edx.split.num_counters_fixed); + printf("Fixed counter width: %d\n", edx.split.bit_width_fixed); + + num_counters = eax.split.num_counters; + if (num_counters > ARRAY_SIZE(gp_events)) + num_counters = ARRAY_SIZE(gp_events); + + apic_write(APIC_LVTPC, PC_VECTOR); + + check_gp_counters(); + check_fixed_counters(); + check_rdpmc(); + check_counters_many(); + check_counter_overflow(); + check_gp_counter_cmask(); + + printf("\n%d tests, %d failures\n", tests, failures); + return !failures ? 0 : 1; +} diff --git a/kvm-unittest/x86/port80.c b/kvm-unittest/x86/port80.c new file mode 100644 index 0000000..522c1a4 --- /dev/null +++ b/kvm-unittest/x86/port80.c @@ -0,0 +1,12 @@ +#include "libcflat.h" + +int main() +{ + int i; + + printf("begining port 0x80 write test\n"); + for (i = 0; i < 10000000; ++i) + asm volatile("outb %al, $0x80"); + printf("done\n"); + return 0; +} diff --git a/kvm-unittest/x86/realmode.c b/kvm-unittest/x86/realmode.c new file mode 100644 index 0000000..c57e033 --- /dev/null +++ b/kvm-unittest/x86/realmode.c @@ -0,0 +1,1627 @@ +#ifndef USE_SERIAL +#define USE_SERIAL +#endif + +asm(".code16gcc"); + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned u32; +typedef unsigned long long u64; + +void test_function(void); + +asm( + "test_function: \n\t" + "mov $0x1234, %eax \n\t" + "ret" + ); + +static int strlen(const char *str) +{ + int n; + + for (n = 0; *str; ++str) + ++n; + return n; +} + +static void outb(u8 data, u16 port) +{ + asm volatile("out %0, %1" : : "a"(data), "d"(port)); +} + +#ifdef USE_SERIAL +static int serial_iobase = 0x3f8; +static int serial_inited = 0; + +static u8 inb(u16 port) +{ + u8 data; + asm volatile("in %1, %0" : "=a"(data) : "d"(port)); + return data; +} + +static void serial_outb(char ch) +{ + u8 lsr; + + do { + lsr = inb(serial_iobase + 0x05); + } while (!(lsr & 0x20)); + + outb(ch, serial_iobase + 0x00); +} + +static void serial_init(void) +{ + u8 lcr; + + /* set DLAB */ + lcr = inb(serial_iobase + 0x03); + lcr |= 0x80; + outb(lcr, serial_iobase + 0x03); + + /* set baud rate to 115200 */ + outb(0x01, serial_iobase + 0x00); + outb(0x00, serial_iobase + 0x01); + + /* clear DLAB */ + lcr = inb(serial_iobase + 0x03); + lcr &= ~0x80; + outb(lcr, serial_iobase + 0x03); +} +#endif + +static void print_serial(const char *buf) +{ + unsigned long len = strlen(buf); +#ifdef USE_SERIAL + unsigned long i; + if (!serial_inited) { + serial_init(); + serial_inited = 1; + } + + for (i = 0; i < len; i++) { + serial_outb(buf[i]); + } +#else + asm volatile ("addr32/rep/outsb" : "+S"(buf), "+c"(len) : "d"(0xf1)); +#endif +} + +static void exit(int code) +{ + outb(code, 0xf4); +} + +struct regs { + u32 eax, ebx, ecx, edx; + u32 esi, edi, esp, ebp; + u32 eip, eflags; +}; + +static u64 gdt[] = { + 0, + 0x00cf9b000000ffffull, // flat 32-bit code segment + 0x00cf93000000ffffull, // flat 32-bit data segment +}; + +static struct { + u16 limit; + void *base; +} __attribute__((packed)) gdt_descr = { + sizeof(gdt) - 1, + gdt, +}; + +struct insn_desc { + u16 ptr; + u16 len; +}; + +static struct regs inregs, outregs; + +static void exec_in_big_real_mode(struct insn_desc *insn) +{ + unsigned long tmp; + static struct regs save; + int i; + extern u8 test_insn[], test_insn_end[]; + + for (i = 0; i < insn->len; ++i) + test_insn[i] = ((u8 *)(unsigned long)insn->ptr)[i]; + for (; i < test_insn_end - test_insn; ++i) + test_insn[i] = 0x90; // nop + + save = inregs; + asm volatile( + "lgdtl %[gdt_descr] \n\t" + "mov %%cr0, %[tmp] \n\t" + "or $1, %[tmp] \n\t" + "mov %[tmp], %%cr0 \n\t" + "mov %[bigseg], %%gs \n\t" + "and $-2, %[tmp] \n\t" + "mov %[tmp], %%cr0 \n\t" + + "pushw %[save]+36; popfw \n\t" + "xchg %%eax, %[save]+0 \n\t" + "xchg %%ebx, %[save]+4 \n\t" + "xchg %%ecx, %[save]+8 \n\t" + "xchg %%edx, %[save]+12 \n\t" + "xchg %%esi, %[save]+16 \n\t" + "xchg %%edi, %[save]+20 \n\t" + "xchg %%esp, %[save]+24 \n\t" + "xchg %%ebp, %[save]+28 \n\t" + + "test_insn: . = . + 32\n\t" + "test_insn_end: \n\t" + + "xchg %%eax, %[save]+0 \n\t" + "xchg %%ebx, %[save]+4 \n\t" + "xchg %%ecx, %[save]+8 \n\t" + "xchg %%edx, %[save]+12 \n\t" + "xchg %%esi, %[save]+16 \n\t" + "xchg %%edi, %[save]+20 \n\t" + "xchg %%esp, %[save]+24 \n\t" + "xchg %%ebp, %[save]+28 \n\t" + + /* Save EFLAGS in outregs*/ + "pushfl \n\t" + "popl %[save]+36 \n\t" + + /* Restore DF for the harness code */ + "cld\n\t" + "xor %[tmp], %[tmp] \n\t" + "mov %[tmp], %%gs \n\t" + : [tmp]"=&r"(tmp), [save]"+m"(save) + : [gdt_descr]"m"(gdt_descr), [bigseg]"r"((short)16) + : "cc", "memory" + ); + outregs = save; +} + +#define R_AX 1 +#define R_BX 2 +#define R_CX 4 +#define R_DX 8 +#define R_SI 16 +#define R_DI 32 +#define R_SP 64 +#define R_BP 128 + +int regs_equal(int ignore) +{ + const u32 *p1 = &inregs.eax, *p2 = &outregs.eax; // yuck + int i; + + for (i = 0; i < 8; ++i) + if (!(ignore & (1 << i)) && p1[i] != p2[i]) + return 0; + return 1; +} + +static void report(const char *name, u16 regs_ignore, _Bool ok) +{ + if (!regs_equal(regs_ignore)) { + ok = 0; + } + print_serial(ok ? "PASS: " : "FAIL: "); + print_serial(name); + print_serial("\n"); +} + +#define MK_INSN(name, str) \ + asm ( \ + ".pushsection .data.insn \n\t" \ + "insn_" #name ": \n\t" \ + ".word 1001f, 1002f - 1001f \n\t" \ + ".popsection \n\t" \ + ".pushsection .text.insn, \"ax\" \n\t" \ + "1001: \n\t" \ + "insn_code_" #name ": " str " \n\t" \ + "1002: \n\t" \ + ".popsection" \ + ); \ + extern struct insn_desc insn_##name; + +void test_xchg(void) +{ + MK_INSN(xchg_test1, "xchg %eax,%eax\n\t"); + MK_INSN(xchg_test2, "xchg %eax,%ebx\n\t"); + MK_INSN(xchg_test3, "xchg %eax,%ecx\n\t"); + MK_INSN(xchg_test4, "xchg %eax,%edx\n\t"); + MK_INSN(xchg_test5, "xchg %eax,%esi\n\t"); + MK_INSN(xchg_test6, "xchg %eax,%edi\n\t"); + MK_INSN(xchg_test7, "xchg %eax,%ebp\n\t"); + MK_INSN(xchg_test8, "xchg %eax,%esp\n\t"); + + inregs = (struct regs){ .eax = 0, .ebx = 1, .ecx = 2, .edx = 3, .esi = 4, .edi = 5, .ebp = 6, .esp = 7}; + + exec_in_big_real_mode(&insn_xchg_test1); + report("xchg 1", 0, 1); + + exec_in_big_real_mode(&insn_xchg_test2); + report("xchg 2", R_AX | R_BX, + outregs.eax == inregs.ebx && outregs.ebx == inregs.eax); + + exec_in_big_real_mode(&insn_xchg_test3); + report("xchg 3", R_AX | R_CX, + outregs.eax == inregs.ecx && outregs.ecx == inregs.eax); + + exec_in_big_real_mode(&insn_xchg_test4); + report("xchg 4", R_AX | R_DX, + outregs.eax == inregs.edx && outregs.edx == inregs.eax); + + exec_in_big_real_mode(&insn_xchg_test5); + report("xchg 5", R_AX | R_SI, + outregs.eax == inregs.esi && outregs.esi == inregs.eax); + + exec_in_big_real_mode(&insn_xchg_test6); + report("xchg 6", R_AX | R_DI, + outregs.eax == inregs.edi && outregs.edi == inregs.eax); + + exec_in_big_real_mode(&insn_xchg_test7); + report("xchg 7", R_AX | R_BP, + outregs.eax == inregs.ebp && outregs.ebp == inregs.eax); + + exec_in_big_real_mode(&insn_xchg_test8); + report("xchg 8", R_AX | R_SP, + outregs.eax == inregs.esp && outregs.esp == inregs.eax); +} + +void test_shld(void) +{ + MK_INSN(shld_test, "shld $8,%edx,%eax\n\t"); + + inregs = (struct regs){ .eax = 0xbe, .edx = 0xef000000 }; + exec_in_big_real_mode(&insn_shld_test); + report("shld", ~0, outregs.eax == 0xbeef); +} + +void test_mov_imm(void) +{ + MK_INSN(mov_r32_imm_1, "mov $1234567890, %eax"); + MK_INSN(mov_r16_imm_1, "mov $1234, %ax"); + MK_INSN(mov_r8_imm_1, "mov $0x12, %ah"); + MK_INSN(mov_r8_imm_2, "mov $0x34, %al"); + MK_INSN(mov_r8_imm_3, "mov $0x12, %ah\n\t" "mov $0x34, %al\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_mov_r16_imm_1); + report("mov 1", R_AX, outregs.eax == 1234); + + /* test mov $imm, %eax */ + exec_in_big_real_mode(&insn_mov_r32_imm_1); + report("mov 2", R_AX, outregs.eax == 1234567890); + + /* test mov $imm, %al/%ah */ + exec_in_big_real_mode(&insn_mov_r8_imm_1); + report("mov 3", R_AX, outregs.eax == 0x1200); + + exec_in_big_real_mode(&insn_mov_r8_imm_2); + report("mov 4", R_AX, outregs.eax == 0x34); + + exec_in_big_real_mode(&insn_mov_r8_imm_3); + report("mov 5", R_AX, outregs.eax == 0x1234); +} + +void test_sub_imm(void) +{ + MK_INSN(sub_r32_imm_1, "mov $1234567890, %eax\n\t" "sub $10, %eax\n\t"); + MK_INSN(sub_r16_imm_1, "mov $1234, %ax\n\t" "sub $10, %ax\n\t"); + MK_INSN(sub_r8_imm_1, "mov $0x12, %ah\n\t" "sub $0x10, %ah\n\t"); + MK_INSN(sub_r8_imm_2, "mov $0x34, %al\n\t" "sub $0x10, %al\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_sub_r16_imm_1); + report("sub 1", R_AX, outregs.eax == 1224); + + /* test mov $imm, %eax */ + exec_in_big_real_mode(&insn_sub_r32_imm_1); + report("sub 2", R_AX, outregs.eax == 1234567880); + + /* test mov $imm, %al/%ah */ + exec_in_big_real_mode(&insn_sub_r8_imm_1); + report("sub 3", R_AX, outregs.eax == 0x0200); + + exec_in_big_real_mode(&insn_sub_r8_imm_2); + report("sub 4", R_AX, outregs.eax == 0x24); +} + +void test_xor_imm(void) +{ + MK_INSN(xor_r32_imm_1, "mov $1234567890, %eax\n\t" "xor $1234567890, %eax\n\t"); + MK_INSN(xor_r16_imm_1, "mov $1234, %ax\n\t" "xor $1234, %ax\n\t"); + MK_INSN(xor_r8_imm_1, "mov $0x12, %ah\n\t" "xor $0x12, %ah\n\t"); + MK_INSN(xor_r8_imm_2, "mov $0x34, %al\n\t" "xor $0x34, %al\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_xor_r16_imm_1); + report("xor 1", R_AX, outregs.eax == 0); + + /* test mov $imm, %eax */ + exec_in_big_real_mode(&insn_xor_r32_imm_1); + report("xor 2", R_AX, outregs.eax == 0); + + /* test mov $imm, %al/%ah */ + exec_in_big_real_mode(&insn_xor_r8_imm_1); + report("xor 3", R_AX, outregs.eax == 0); + + exec_in_big_real_mode(&insn_xor_r8_imm_2); + report("xor 4", R_AX, outregs.eax == 0); +} + +void test_cmp_imm(void) +{ + MK_INSN(cmp_test1, "mov $0x34, %al\n\t" + "cmp $0x34, %al\n\t"); + MK_INSN(cmp_test2, "mov $0x34, %al\n\t" + "cmp $0x39, %al\n\t"); + MK_INSN(cmp_test3, "mov $0x34, %al\n\t" + "cmp $0x24, %al\n\t"); + + inregs = (struct regs){ 0 }; + + /* test cmp imm8 with AL */ + /* ZF: (bit 6) Zero Flag becomes 1 if an operation results + * in a 0 writeback, or 0 register + */ + exec_in_big_real_mode(&insn_cmp_test1); + report("cmp 1", ~0, (outregs.eflags & (1<<6)) == (1<<6)); + + exec_in_big_real_mode(&insn_cmp_test2); + report("cmp 2", ~0, (outregs.eflags & (1<<6)) == 0); + + exec_in_big_real_mode(&insn_cmp_test3); + report("cmp 3", ~0, (outregs.eflags & (1<<6)) == 0); +} + +void test_add_imm(void) +{ + MK_INSN(add_test1, "mov $0x43211234, %eax \n\t" + "add $0x12344321, %eax \n\t"); + MK_INSN(add_test2, "mov $0x12, %eax \n\t" + "add $0x21, %al\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_add_test1); + report("add 1", ~0, outregs.eax == 0x55555555); + + exec_in_big_real_mode(&insn_add_test2); + report("add 2", ~0, outregs.eax == 0x33); +} + +void test_eflags_insn(void) +{ + MK_INSN(clc, "clc"); + MK_INSN(stc, "stc"); + MK_INSN(cli, "cli"); + MK_INSN(sti, "sti"); + MK_INSN(cld, "cld"); + MK_INSN(std, "std"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_clc); + report("clc", ~0, (outregs.eflags & 1) == 0); + + exec_in_big_real_mode(&insn_stc); + report("stc", ~0, (outregs.eflags & 1) == 1); + + exec_in_big_real_mode(&insn_cli); + report("cli", ~0, !(outregs.eflags & (1 << 9))); + + exec_in_big_real_mode(&insn_sti); + report("sti", ~0, outregs.eflags & (1 << 9)); + + exec_in_big_real_mode(&insn_cld); + report("cld", ~0, !(outregs.eflags & (1 << 10))); + + exec_in_big_real_mode(&insn_std); + report("std", ~0, (outregs.eflags & (1 << 10))); +} + +void test_io(void) +{ + MK_INSN(io_test1, "mov $0xff, %al \n\t" + "out %al, $0xe0 \n\t" + "mov $0x00, %al \n\t" + "in $0xe0, %al \n\t"); + MK_INSN(io_test2, "mov $0xffff, %ax \n\t" + "out %ax, $0xe0 \n\t" + "mov $0x0000, %ax \n\t" + "in $0xe0, %ax \n\t"); + MK_INSN(io_test3, "mov $0xffffffff, %eax \n\t" + "out %eax, $0xe0 \n\t" + "mov $0x000000, %eax \n\t" + "in $0xe0, %eax \n\t"); + MK_INSN(io_test4, "mov $0xe0, %dx \n\t" + "mov $0xff, %al \n\t" + "out %al, %dx \n\t" + "mov $0x00, %al \n\t" + "in %dx, %al \n\t"); + MK_INSN(io_test5, "mov $0xe0, %dx \n\t" + "mov $0xffff, %ax \n\t" + "out %ax, %dx \n\t" + "mov $0x0000, %ax \n\t" + "in %dx, %ax \n\t"); + MK_INSN(io_test6, "mov $0xe0, %dx \n\t" + "mov $0xffffffff, %eax \n\t" + "out %eax, %dx \n\t" + "mov $0x00000000, %eax \n\t" + "in %dx, %eax \n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_io_test1); + report("pio 1", R_AX, outregs.eax == 0xff); + + exec_in_big_real_mode(&insn_io_test2); + report("pio 2", R_AX, outregs.eax == 0xffff); + + exec_in_big_real_mode(&insn_io_test3); + report("pio 3", R_AX, outregs.eax == 0xffffffff); + + exec_in_big_real_mode(&insn_io_test4); + report("pio 4", R_AX|R_DX, outregs.eax == 0xff); + + exec_in_big_real_mode(&insn_io_test5); + report("pio 5", R_AX|R_DX, outregs.eax == 0xffff); + + exec_in_big_real_mode(&insn_io_test6); + report("pio 6", R_AX|R_DX, outregs.eax == 0xffffffff); +} + +asm ("retf: lretw"); +extern void retf(); + +asm ("retf_imm: lretw $10"); +extern void retf_imm(); + +void test_call(void) +{ + u32 esp[16]; + u32 addr; + + inregs = (struct regs){ 0 }; + inregs.esp = (u32)esp; + + MK_INSN(call1, "mov $test_function, %eax \n\t" + "call *%eax\n\t"); + MK_INSN(call_near1, "jmp 2f\n\t" + "1: mov $0x1234, %eax\n\t" + "ret\n\t" + "2: call 1b\t"); + MK_INSN(call_near2, "call 1f\n\t" + "jmp 2f\n\t" + "1: mov $0x1234, %eax\n\t" + "ret\n\t" + "2:\t"); + MK_INSN(call_far1, "lcallw *(%ebx)\n\t"); + MK_INSN(call_far2, "lcallw $0, $retf\n\t"); + MK_INSN(ret_imm, "sub $10, %sp; jmp 2f; 1: retw $10; 2: callw 1b"); + MK_INSN(retf_imm, "sub $10, %sp; lcallw $0, $retf_imm"); + + exec_in_big_real_mode(&insn_call1); + report("call 1", R_AX, outregs.eax == 0x1234); + + exec_in_big_real_mode(&insn_call_near1); + report("call near 1", R_AX, outregs.eax == 0x1234); + + exec_in_big_real_mode(&insn_call_near2); + report("call near 2", R_AX, outregs.eax == 0x1234); + + addr = (((unsigned)retf >> 4) << 16) | ((unsigned)retf & 0x0f); + inregs.ebx = (unsigned)&addr; + exec_in_big_real_mode(&insn_call_far1); + report("call far 1", 0, 1); + + exec_in_big_real_mode(&insn_call_far2); + report("call far 2", 0, 1); + + exec_in_big_real_mode(&insn_ret_imm); + report("ret imm 1", 0, 1); + + exec_in_big_real_mode(&insn_retf_imm); + report("retf imm 1", 0, 1); +} + +void test_jcc_short(void) +{ + MK_INSN(jnz_short1, "jnz 1f\n\t" + "mov $0x1234, %eax\n\t" + "1:\n\t"); + MK_INSN(jnz_short2, "1:\n\t" + "cmp $0x1234, %eax\n\t" + "mov $0x1234, %eax\n\t" + "jnz 1b\n\t"); + MK_INSN(jmp_short1, "jmp 1f\n\t" + "mov $0x1234, %eax\n\t" + "1:\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_jnz_short1); + report("jnz short 1", ~0, 1); + + exec_in_big_real_mode(&insn_jnz_short2); + report("jnz short 2", R_AX, (outregs.eflags & (1 << 6))); + + exec_in_big_real_mode(&insn_jmp_short1); + report("jmp short 1", ~0, 1); +} + +void test_jcc_near(void) +{ + /* encode near jmp manually. gas will not do it if offsets < 127 byte */ + MK_INSN(jnz_near1, ".byte 0x0f, 0x85, 0x06, 0x00\n\t" + "mov $0x1234, %eax\n\t"); + MK_INSN(jnz_near2, "cmp $0x1234, %eax\n\t" + "mov $0x1234, %eax\n\t" + ".byte 0x0f, 0x85, 0xf0, 0xff\n\t"); + MK_INSN(jmp_near1, ".byte 0xE9, 0x06, 0x00\n\t" + "mov $0x1234, %eax\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_jnz_near1); + report("jnz near 1", 0, 1); + + exec_in_big_real_mode(&insn_jnz_near2); + report("jnz near 2", R_AX, outregs.eflags & (1 << 6)); + + exec_in_big_real_mode(&insn_jmp_near1); + report("jmp near 1", 0, 1); +} + +void test_long_jmp() +{ + u32 esp[16]; + + inregs = (struct regs){ 0 }; + inregs.esp = (u32)(esp+16); + MK_INSN(long_jmp, "call 1f\n\t" + "jmp 2f\n\t" + "1: jmp $0, $test_function\n\t" + "2:\n\t"); + exec_in_big_real_mode(&insn_long_jmp); + report("jmp far 1", R_AX, outregs.eax == 0x1234); +} + +void test_push_pop() +{ + MK_INSN(push32, "mov $0x12345678, %eax\n\t" + "push %eax\n\t" + "pop %ebx\n\t"); + MK_INSN(push16, "mov $0x1234, %ax\n\t" + "push %ax\n\t" + "pop %bx\n\t"); + + MK_INSN(push_es, "mov $0x231, %bx\n\t" //Just write a dummy value to see if it gets overwritten + "mov $0x123, %ax\n\t" + "mov %ax, %es\n\t" + "push %es\n\t" + "pop %bx \n\t" + ); + MK_INSN(pop_es, "push %ax\n\t" + "pop %es\n\t" + "mov %es, %bx\n\t" + ); + MK_INSN(push_pop_ss, "push %ss\n\t" + "pushw %ax\n\t" + "popw %ss\n\t" + "mov %ss, %bx\n\t" + "pop %ss\n\t" + ); + MK_INSN(push_pop_fs, "push %fs\n\t" + "pushl %eax\n\t" + "popl %fs\n\t" + "mov %fs, %ebx\n\t" + "pop %fs\n\t" + ); + MK_INSN(push_pop_high_esp_bits, + "xor $0x12340000, %esp \n\t" + "push %ax; \n\t" + "xor $0x12340000, %esp \n\t" + "pop %bx"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_push32); + report("push/pop 1", R_AX|R_BX, + outregs.eax == outregs.ebx && outregs.eax == 0x12345678); + + exec_in_big_real_mode(&insn_push16); + report("push/pop 2", R_AX|R_BX, + outregs.eax == outregs.ebx && outregs.eax == 0x1234); + + exec_in_big_real_mode(&insn_push_es); + report("push/pop 3", R_AX|R_BX, + outregs.ebx == outregs.eax && outregs.eax == 0x123); + + exec_in_big_real_mode(&insn_pop_es); + report("push/pop 4", R_AX|R_BX, outregs.ebx == outregs.eax); + + exec_in_big_real_mode(&insn_push_pop_ss); + report("push/pop 5", R_AX|R_BX, outregs.ebx == outregs.eax); + + exec_in_big_real_mode(&insn_push_pop_fs); + report("push/pop 6", R_AX|R_BX, outregs.ebx == outregs.eax); + + inregs.eax = 0x9977; + inregs.ebx = 0x7799; + exec_in_big_real_mode(&insn_push_pop_high_esp_bits); + report("push/pop with high bits set in %esp", R_BX, outregs.ebx == 0x9977); +} + +void test_null(void) +{ + MK_INSN(null, ""); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_null); + report("null", 0, 1); +} + +struct { + char stack[500]; + char top[]; +} tmp_stack; + +void test_pusha_popa() +{ + MK_INSN(pusha, "pusha\n\t" + "pop %edi\n\t" + "pop %esi\n\t" + "pop %ebp\n\t" + "add $4, %esp\n\t" + "pop %ebx\n\t" + "pop %edx\n\t" + "pop %ecx\n\t" + "pop %eax\n\t" + ); + + MK_INSN(popa, "push %eax\n\t" + "push %ecx\n\t" + "push %edx\n\t" + "push %ebx\n\t" + "push %esp\n\t" + "push %ebp\n\t" + "push %esi\n\t" + "push %edi\n\t" + "popa\n\t" + ); + + inregs = (struct regs){ .eax = 0, .ebx = 1, .ecx = 2, .edx = 3, .esi = 4, .edi = 5, .ebp = 6, .esp = (unsigned long)&tmp_stack.top }; + + exec_in_big_real_mode(&insn_pusha); + report("pusha/popa 1", 0, 1); + + exec_in_big_real_mode(&insn_popa); + report("pusha/popa 1", 0, 1); +} + +void test_iret() +{ + MK_INSN(iret32, "pushf\n\t" + "pushl %cs\n\t" + "call 1f\n\t" /* a near call will push eip onto the stack */ + "jmp 2f\n\t" + "1: iret\n\t" + "2:\n\t" + ); + + MK_INSN(iret16, "pushfw\n\t" + "pushw %cs\n\t" + "callw 1f\n\t" + "jmp 2f\n\t" + "1: iretw\n\t" + "2:\n\t"); + + MK_INSN(iret_flags32, "pushfl\n\t" + "popl %eax\n\t" + "andl $~0x2, %eax\n\t" + "orl $0xffc08028, %eax\n\t" + "pushl %eax\n\t" + "pushl %cs\n\t" + "call 1f\n\t" + "jmp 2f\n\t" + "1: iret\n\t" + "2:\n\t"); + + MK_INSN(iret_flags16, "pushfw\n\t" + "popw %ax\n\t" + "and $~0x2, %ax\n\t" + "or $0x8028, %ax\n\t" + "pushw %ax\n\t" + "pushw %cs\n\t" + "callw 1f\n\t" + "jmp 2f\n\t" + "1: iretw\n\t" + "2:\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_iret32); + report("iret 1", 0, 1); + + exec_in_big_real_mode(&insn_iret16); + report("iret 2", 0, 1); + + exec_in_big_real_mode(&insn_iret_flags32); + report("iret 3", R_AX, 1); + + exec_in_big_real_mode(&insn_iret_flags16); + report("iret 4", R_AX, 1); +} + +void test_int() +{ + inregs = (struct regs){ 0 }; + + *(u32 *)(0x11 * 4) = 0x1000; /* Store a pointer to address 0x1000 in IDT entry 0x11 */ + *(u8 *)(0x1000) = 0xcf; /* 0x1000 contains an IRET instruction */ + + MK_INSN(int11, "int $0x11\n\t"); + + exec_in_big_real_mode(&insn_int11); + report("int 1", 0, 1); +} + +void test_imul() +{ + MK_INSN(imul8_1, "mov $2, %al\n\t" + "mov $-4, %cx\n\t" + "imul %cl\n\t"); + + MK_INSN(imul16_1, "mov $2, %ax\n\t" + "mov $-4, %cx\n\t" + "imul %cx\n\t"); + + MK_INSN(imul32_1, "mov $2, %eax\n\t" + "mov $-4, %ecx\n\t" + "imul %ecx\n\t"); + + MK_INSN(imul8_2, "mov $0x12340002, %eax\n\t" + "mov $4, %cx\n\t" + "imul %cl\n\t"); + + MK_INSN(imul16_2, "mov $2, %ax\n\t" + "mov $4, %cx\n\t" + "imul %cx\n\t"); + + MK_INSN(imul32_2, "mov $2, %eax\n\t" + "mov $4, %ecx\n\t" + "imul %ecx\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_imul8_1); + report("imul 1", R_AX | R_CX | R_DX, (outregs.eax & 0xff) == (u8)-8); + + exec_in_big_real_mode(&insn_imul16_1); + report("imul 2", R_AX | R_CX | R_DX, outregs.eax == (u16)-8); + + exec_in_big_real_mode(&insn_imul32_1); + report("imul 3", R_AX | R_CX | R_DX, outregs.eax == (u32)-8); + + exec_in_big_real_mode(&insn_imul8_2); + report("imul 4", R_AX | R_CX | R_DX, + (outregs.eax & 0xffff) == 8 + && (outregs.eax & 0xffff0000) == 0x12340000); + + exec_in_big_real_mode(&insn_imul16_2); + report("imul 5", R_AX | R_CX | R_DX, outregs.eax == 8); + + exec_in_big_real_mode(&insn_imul32_2); + report("imul 6", R_AX | R_CX | R_DX, outregs.eax == 8); +} + +void test_mul() +{ + MK_INSN(mul8, "mov $2, %al\n\t" + "mov $4, %cx\n\t" + "imul %cl\n\t"); + + MK_INSN(mul16, "mov $2, %ax\n\t" + "mov $4, %cx\n\t" + "imul %cx\n\t"); + + MK_INSN(mul32, "mov $2, %eax\n\t" + "mov $4, %ecx\n\t" + "imul %ecx\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_mul8); + report("mul 1", R_AX | R_CX | R_DX, (outregs.eax & 0xff) == 8); + + exec_in_big_real_mode(&insn_mul16); + report("mul 2", R_AX | R_CX | R_DX, outregs.eax == 8); + + exec_in_big_real_mode(&insn_mul32); + report("mul 3", R_AX | R_CX | R_DX, outregs.eax == 8); +} + +void test_div() +{ + MK_INSN(div8, "mov $257, %ax\n\t" + "mov $2, %cl\n\t" + "div %cl\n\t"); + + MK_INSN(div16, "mov $512, %ax\n\t" + "mov $5, %cx\n\t" + "div %cx\n\t"); + + MK_INSN(div32, "mov $512, %eax\n\t" + "mov $5, %ecx\n\t" + "div %ecx\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_div8); + report("div 1", R_AX | R_CX | R_DX, outregs.eax == 384); + + exec_in_big_real_mode(&insn_div16); + report("div 2", R_AX | R_CX | R_DX, + outregs.eax == 102 && outregs.edx == 2); + + exec_in_big_real_mode(&insn_div32); + report("div 3", R_AX | R_CX | R_DX, + outregs.eax == 102 && outregs.edx == 2); +} + +void test_idiv() +{ + MK_INSN(idiv8, "mov $256, %ax\n\t" + "mov $-2, %cl\n\t" + "idiv %cl\n\t"); + + MK_INSN(idiv16, "mov $512, %ax\n\t" + "mov $-2, %cx\n\t" + "idiv %cx\n\t"); + + MK_INSN(idiv32, "mov $512, %eax\n\t" + "mov $-2, %ecx\n\t" + "idiv %ecx\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_idiv8); + report("idiv 1", R_AX | R_CX | R_DX, outregs.eax == (u8)-128); + + exec_in_big_real_mode(&insn_idiv16); + report("idiv 2", R_AX | R_CX | R_DX, outregs.eax == (u16)-256); + + exec_in_big_real_mode(&insn_idiv32); + report("idiv 3", R_AX | R_CX | R_DX, outregs.eax == (u32)-256); +} + +void test_cbw(void) +{ + MK_INSN(cbw, "mov $0xFE, %eax \n\t" + "cbw\n\t"); + MK_INSN(cwde, "mov $0xFFFE, %eax \n\t" + "cwde\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_cbw); + report("cbq 1", ~0, outregs.eax == 0xFFFE); + + exec_in_big_real_mode(&insn_cwde); + report("cwde 1", ~0, outregs.eax == 0xFFFFFFFE); +} + +void test_loopcc(void) +{ + MK_INSN(loop, "mov $10, %ecx\n\t" + "1: inc %eax\n\t" + "loop 1b\n\t"); + + MK_INSN(loope, "mov $10, %ecx\n\t" + "mov $1, %eax\n\t" + "1: dec %eax\n\t" + "loope 1b\n\t"); + + MK_INSN(loopne, "mov $10, %ecx\n\t" + "mov $5, %eax\n\t" + "1: dec %eax\n\t" + "loopne 1b\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_loop); + report("LOOPcc short 1", R_AX, outregs.eax == 10); + + exec_in_big_real_mode(&insn_loope); + report("LOOPcc short 2", R_AX | R_CX, + outregs.eax == -1 && outregs.ecx == 8); + + exec_in_big_real_mode(&insn_loopne); + report("LOOPcc short 3", R_AX | R_CX, + outregs.eax == 0 && outregs.ecx == 5); +} + +static void test_das(void) +{ + short i; + u16 nr_fail = 0; + static unsigned test_cases[1024] = { + 0x46000000, 0x8701a000, 0x9710fa00, 0x97119a00, + 0x02000101, 0x8301a101, 0x9310fb01, 0x93119b01, + 0x02000202, 0x8301a202, 0x9710fc02, 0x97119c02, + 0x06000303, 0x8701a303, 0x9310fd03, 0x93119d03, + 0x02000404, 0x8301a404, 0x9310fe04, 0x93119e04, + 0x06000505, 0x8701a505, 0x9710ff05, 0x97119f05, + 0x06000606, 0x8701a606, 0x56100006, 0x9711a006, + 0x02000707, 0x8301a707, 0x12100107, 0x9311a107, + 0x02000808, 0x8301a808, 0x12100208, 0x9311a208, + 0x06000909, 0x8701a909, 0x16100309, 0x9711a309, + 0x1200040a, 0x9301a40a, 0x1210040a, 0x9311a40a, + 0x1600050b, 0x9701a50b, 0x1610050b, 0x9711a50b, + 0x1600060c, 0x9701a60c, 0x1610060c, 0x9711a60c, + 0x1200070d, 0x9301a70d, 0x1210070d, 0x9311a70d, + 0x1200080e, 0x9301a80e, 0x1210080e, 0x9311a80e, + 0x1600090f, 0x9701a90f, 0x1610090f, 0x9711a90f, + 0x02001010, 0x8301b010, 0x16100a10, 0x9711aa10, + 0x06001111, 0x8701b111, 0x12100b11, 0x9311ab11, + 0x06001212, 0x8701b212, 0x16100c12, 0x9711ac12, + 0x02001313, 0x8301b313, 0x12100d13, 0x9311ad13, + 0x06001414, 0x8701b414, 0x12100e14, 0x9311ae14, + 0x02001515, 0x8301b515, 0x16100f15, 0x9711af15, + 0x02001616, 0x8301b616, 0x12101016, 0x9311b016, + 0x06001717, 0x8701b717, 0x16101117, 0x9711b117, + 0x06001818, 0x8701b818, 0x16101218, 0x9711b218, + 0x02001919, 0x8301b919, 0x12101319, 0x9311b319, + 0x1600141a, 0x9701b41a, 0x1610141a, 0x9711b41a, + 0x1200151b, 0x9301b51b, 0x1210151b, 0x9311b51b, + 0x1200161c, 0x9301b61c, 0x1210161c, 0x9311b61c, + 0x1600171d, 0x9701b71d, 0x1610171d, 0x9711b71d, + 0x1600181e, 0x9701b81e, 0x1610181e, 0x9711b81e, + 0x1200191f, 0x9301b91f, 0x1210191f, 0x9311b91f, + 0x02002020, 0x8701c020, 0x12101a20, 0x9311ba20, + 0x06002121, 0x8301c121, 0x16101b21, 0x9711bb21, + 0x06002222, 0x8301c222, 0x12101c22, 0x9311bc22, + 0x02002323, 0x8701c323, 0x16101d23, 0x9711bd23, + 0x06002424, 0x8301c424, 0x16101e24, 0x9711be24, + 0x02002525, 0x8701c525, 0x12101f25, 0x9311bf25, + 0x02002626, 0x8701c626, 0x12102026, 0x9711c026, + 0x06002727, 0x8301c727, 0x16102127, 0x9311c127, + 0x06002828, 0x8301c828, 0x16102228, 0x9311c228, + 0x02002929, 0x8701c929, 0x12102329, 0x9711c329, + 0x1600242a, 0x9301c42a, 0x1610242a, 0x9311c42a, + 0x1200252b, 0x9701c52b, 0x1210252b, 0x9711c52b, + 0x1200262c, 0x9701c62c, 0x1210262c, 0x9711c62c, + 0x1600272d, 0x9301c72d, 0x1610272d, 0x9311c72d, + 0x1600282e, 0x9301c82e, 0x1610282e, 0x9311c82e, + 0x1200292f, 0x9701c92f, 0x1210292f, 0x9711c92f, + 0x06003030, 0x8301d030, 0x12102a30, 0x9711ca30, + 0x02003131, 0x8701d131, 0x16102b31, 0x9311cb31, + 0x02003232, 0x8701d232, 0x12102c32, 0x9711cc32, + 0x06003333, 0x8301d333, 0x16102d33, 0x9311cd33, + 0x02003434, 0x8701d434, 0x16102e34, 0x9311ce34, + 0x06003535, 0x8301d535, 0x12102f35, 0x9711cf35, + 0x06003636, 0x8301d636, 0x16103036, 0x9311d036, + 0x02003737, 0x8701d737, 0x12103137, 0x9711d137, + 0x02003838, 0x8701d838, 0x12103238, 0x9711d238, + 0x06003939, 0x8301d939, 0x16103339, 0x9311d339, + 0x1200343a, 0x9701d43a, 0x1210343a, 0x9711d43a, + 0x1600353b, 0x9301d53b, 0x1610353b, 0x9311d53b, + 0x1600363c, 0x9301d63c, 0x1610363c, 0x9311d63c, + 0x1200373d, 0x9701d73d, 0x1210373d, 0x9711d73d, + 0x1200383e, 0x9701d83e, 0x1210383e, 0x9711d83e, + 0x1600393f, 0x9301d93f, 0x1610393f, 0x9311d93f, + 0x02004040, 0x8301e040, 0x16103a40, 0x9311da40, + 0x06004141, 0x8701e141, 0x12103b41, 0x9711db41, + 0x06004242, 0x8701e242, 0x16103c42, 0x9311dc42, + 0x02004343, 0x8301e343, 0x12103d43, 0x9711dd43, + 0x06004444, 0x8701e444, 0x12103e44, 0x9711de44, + 0x02004545, 0x8301e545, 0x16103f45, 0x9311df45, + 0x02004646, 0x8301e646, 0x12104046, 0x9311e046, + 0x06004747, 0x8701e747, 0x16104147, 0x9711e147, + 0x06004848, 0x8701e848, 0x16104248, 0x9711e248, + 0x02004949, 0x8301e949, 0x12104349, 0x9311e349, + 0x1600444a, 0x9701e44a, 0x1610444a, 0x9711e44a, + 0x1200454b, 0x9301e54b, 0x1210454b, 0x9311e54b, + 0x1200464c, 0x9301e64c, 0x1210464c, 0x9311e64c, + 0x1600474d, 0x9701e74d, 0x1610474d, 0x9711e74d, + 0x1600484e, 0x9701e84e, 0x1610484e, 0x9711e84e, + 0x1200494f, 0x9301e94f, 0x1210494f, 0x9311e94f, + 0x06005050, 0x8701f050, 0x12104a50, 0x9311ea50, + 0x02005151, 0x8301f151, 0x16104b51, 0x9711eb51, + 0x02005252, 0x8301f252, 0x12104c52, 0x9311ec52, + 0x06005353, 0x8701f353, 0x16104d53, 0x9711ed53, + 0x02005454, 0x8301f454, 0x16104e54, 0x9711ee54, + 0x06005555, 0x8701f555, 0x12104f55, 0x9311ef55, + 0x06005656, 0x8701f656, 0x16105056, 0x9711f056, + 0x02005757, 0x8301f757, 0x12105157, 0x9311f157, + 0x02005858, 0x8301f858, 0x12105258, 0x9311f258, + 0x06005959, 0x8701f959, 0x16105359, 0x9711f359, + 0x1200545a, 0x9301f45a, 0x1210545a, 0x9311f45a, + 0x1600555b, 0x9701f55b, 0x1610555b, 0x9711f55b, + 0x1600565c, 0x9701f65c, 0x1610565c, 0x9711f65c, + 0x1200575d, 0x9301f75d, 0x1210575d, 0x9311f75d, + 0x1200585e, 0x9301f85e, 0x1210585e, 0x9311f85e, + 0x1600595f, 0x9701f95f, 0x1610595f, 0x9711f95f, + 0x06006060, 0x47010060, 0x16105a60, 0x9711fa60, + 0x02006161, 0x03010161, 0x12105b61, 0x9311fb61, + 0x02006262, 0x03010262, 0x16105c62, 0x9711fc62, + 0x06006363, 0x07010363, 0x12105d63, 0x9311fd63, + 0x02006464, 0x03010464, 0x12105e64, 0x9311fe64, + 0x06006565, 0x07010565, 0x16105f65, 0x9711ff65, + 0x06006666, 0x07010666, 0x16106066, 0x57110066, + 0x02006767, 0x03010767, 0x12106167, 0x13110167, + 0x02006868, 0x03010868, 0x12106268, 0x13110268, + 0x06006969, 0x07010969, 0x16106369, 0x17110369, + 0x1200646a, 0x1301046a, 0x1210646a, 0x1311046a, + 0x1600656b, 0x1701056b, 0x1610656b, 0x1711056b, + 0x1600666c, 0x1701066c, 0x1610666c, 0x1711066c, + 0x1200676d, 0x1301076d, 0x1210676d, 0x1311076d, + 0x1200686e, 0x1301086e, 0x1210686e, 0x1311086e, + 0x1600696f, 0x1701096f, 0x1610696f, 0x1711096f, + 0x02007070, 0x03011070, 0x16106a70, 0x17110a70, + 0x06007171, 0x07011171, 0x12106b71, 0x13110b71, + 0x06007272, 0x07011272, 0x16106c72, 0x17110c72, + 0x02007373, 0x03011373, 0x12106d73, 0x13110d73, + 0x06007474, 0x07011474, 0x12106e74, 0x13110e74, + 0x02007575, 0x03011575, 0x16106f75, 0x17110f75, + 0x02007676, 0x03011676, 0x12107076, 0x13111076, + 0x06007777, 0x07011777, 0x16107177, 0x17111177, + 0x06007878, 0x07011878, 0x16107278, 0x17111278, + 0x02007979, 0x03011979, 0x12107379, 0x13111379, + 0x1600747a, 0x1701147a, 0x1610747a, 0x1711147a, + 0x1200757b, 0x1301157b, 0x1210757b, 0x1311157b, + 0x1200767c, 0x1301167c, 0x1210767c, 0x1311167c, + 0x1600777d, 0x1701177d, 0x1610777d, 0x1711177d, + 0x1600787e, 0x1701187e, 0x1610787e, 0x1711187e, + 0x1200797f, 0x1301197f, 0x1210797f, 0x1311197f, + 0x82008080, 0x03012080, 0x12107a80, 0x13111a80, + 0x86008181, 0x07012181, 0x16107b81, 0x17111b81, + 0x86008282, 0x07012282, 0x12107c82, 0x13111c82, + 0x82008383, 0x03012383, 0x16107d83, 0x17111d83, + 0x86008484, 0x07012484, 0x16107e84, 0x17111e84, + 0x82008585, 0x03012585, 0x12107f85, 0x13111f85, + 0x82008686, 0x03012686, 0x92108086, 0x13112086, + 0x86008787, 0x07012787, 0x96108187, 0x17112187, + 0x86008888, 0x07012888, 0x96108288, 0x17112288, + 0x82008989, 0x03012989, 0x92108389, 0x13112389, + 0x9600848a, 0x1701248a, 0x9610848a, 0x1711248a, + 0x9200858b, 0x1301258b, 0x9210858b, 0x1311258b, + 0x9200868c, 0x1301268c, 0x9210868c, 0x1311268c, + 0x9600878d, 0x1701278d, 0x9610878d, 0x1711278d, + 0x9600888e, 0x1701288e, 0x9610888e, 0x1711288e, + 0x9200898f, 0x1301298f, 0x9210898f, 0x1311298f, + 0x86009090, 0x07013090, 0x92108a90, 0x13112a90, + 0x82009191, 0x03013191, 0x96108b91, 0x17112b91, + 0x82009292, 0x03013292, 0x92108c92, 0x13112c92, + 0x86009393, 0x07013393, 0x96108d93, 0x17112d93, + 0x82009494, 0x03013494, 0x96108e94, 0x17112e94, + 0x86009595, 0x07013595, 0x92108f95, 0x13112f95, + 0x86009696, 0x07013696, 0x96109096, 0x17113096, + 0x82009797, 0x03013797, 0x92109197, 0x13113197, + 0x82009898, 0x03013898, 0x92109298, 0x13113298, + 0x86009999, 0x07013999, 0x96109399, 0x17113399, + 0x1300349a, 0x1301349a, 0x1310349a, 0x1311349a, + 0x1700359b, 0x1701359b, 0x1710359b, 0x1711359b, + 0x1700369c, 0x1701369c, 0x1710369c, 0x1711369c, + 0x1300379d, 0x1301379d, 0x1310379d, 0x1311379d, + 0x1300389e, 0x1301389e, 0x1310389e, 0x1311389e, + 0x1700399f, 0x1701399f, 0x1710399f, 0x1711399f, + 0x030040a0, 0x030140a0, 0x17103aa0, 0x17113aa0, + 0x070041a1, 0x070141a1, 0x13103ba1, 0x13113ba1, + 0x070042a2, 0x070142a2, 0x17103ca2, 0x17113ca2, + 0x030043a3, 0x030143a3, 0x13103da3, 0x13113da3, + 0x070044a4, 0x070144a4, 0x13103ea4, 0x13113ea4, + 0x030045a5, 0x030145a5, 0x17103fa5, 0x17113fa5, + 0x030046a6, 0x030146a6, 0x131040a6, 0x131140a6, + 0x070047a7, 0x070147a7, 0x171041a7, 0x171141a7, + 0x070048a8, 0x070148a8, 0x171042a8, 0x171142a8, + 0x030049a9, 0x030149a9, 0x131043a9, 0x131143a9, + 0x170044aa, 0x170144aa, 0x171044aa, 0x171144aa, + 0x130045ab, 0x130145ab, 0x131045ab, 0x131145ab, + 0x130046ac, 0x130146ac, 0x131046ac, 0x131146ac, + 0x170047ad, 0x170147ad, 0x171047ad, 0x171147ad, + 0x170048ae, 0x170148ae, 0x171048ae, 0x171148ae, + 0x130049af, 0x130149af, 0x131049af, 0x131149af, + 0x070050b0, 0x070150b0, 0x13104ab0, 0x13114ab0, + 0x030051b1, 0x030151b1, 0x17104bb1, 0x17114bb1, + 0x030052b2, 0x030152b2, 0x13104cb2, 0x13114cb2, + 0x070053b3, 0x070153b3, 0x17104db3, 0x17114db3, + 0x030054b4, 0x030154b4, 0x17104eb4, 0x17114eb4, + 0x070055b5, 0x070155b5, 0x13104fb5, 0x13114fb5, + 0x070056b6, 0x070156b6, 0x171050b6, 0x171150b6, + 0x030057b7, 0x030157b7, 0x131051b7, 0x131151b7, + 0x030058b8, 0x030158b8, 0x131052b8, 0x131152b8, + 0x070059b9, 0x070159b9, 0x171053b9, 0x171153b9, + 0x130054ba, 0x130154ba, 0x131054ba, 0x131154ba, + 0x170055bb, 0x170155bb, 0x171055bb, 0x171155bb, + 0x170056bc, 0x170156bc, 0x171056bc, 0x171156bc, + 0x130057bd, 0x130157bd, 0x131057bd, 0x131157bd, + 0x130058be, 0x130158be, 0x131058be, 0x131158be, + 0x170059bf, 0x170159bf, 0x171059bf, 0x171159bf, + 0x070060c0, 0x070160c0, 0x17105ac0, 0x17115ac0, + 0x030061c1, 0x030161c1, 0x13105bc1, 0x13115bc1, + 0x030062c2, 0x030162c2, 0x17105cc2, 0x17115cc2, + 0x070063c3, 0x070163c3, 0x13105dc3, 0x13115dc3, + 0x030064c4, 0x030164c4, 0x13105ec4, 0x13115ec4, + 0x070065c5, 0x070165c5, 0x17105fc5, 0x17115fc5, + 0x070066c6, 0x070166c6, 0x171060c6, 0x171160c6, + 0x030067c7, 0x030167c7, 0x131061c7, 0x131161c7, + 0x030068c8, 0x030168c8, 0x131062c8, 0x131162c8, + 0x070069c9, 0x070169c9, 0x171063c9, 0x171163c9, + 0x130064ca, 0x130164ca, 0x131064ca, 0x131164ca, + 0x170065cb, 0x170165cb, 0x171065cb, 0x171165cb, + 0x170066cc, 0x170166cc, 0x171066cc, 0x171166cc, + 0x130067cd, 0x130167cd, 0x131067cd, 0x131167cd, + 0x130068ce, 0x130168ce, 0x131068ce, 0x131168ce, + 0x170069cf, 0x170169cf, 0x171069cf, 0x171169cf, + 0x030070d0, 0x030170d0, 0x17106ad0, 0x17116ad0, + 0x070071d1, 0x070171d1, 0x13106bd1, 0x13116bd1, + 0x070072d2, 0x070172d2, 0x17106cd2, 0x17116cd2, + 0x030073d3, 0x030173d3, 0x13106dd3, 0x13116dd3, + 0x070074d4, 0x070174d4, 0x13106ed4, 0x13116ed4, + 0x030075d5, 0x030175d5, 0x17106fd5, 0x17116fd5, + 0x030076d6, 0x030176d6, 0x131070d6, 0x131170d6, + 0x070077d7, 0x070177d7, 0x171071d7, 0x171171d7, + 0x070078d8, 0x070178d8, 0x171072d8, 0x171172d8, + 0x030079d9, 0x030179d9, 0x131073d9, 0x131173d9, + 0x170074da, 0x170174da, 0x171074da, 0x171174da, + 0x130075db, 0x130175db, 0x131075db, 0x131175db, + 0x130076dc, 0x130176dc, 0x131076dc, 0x131176dc, + 0x170077dd, 0x170177dd, 0x171077dd, 0x171177dd, + 0x170078de, 0x170178de, 0x171078de, 0x171178de, + 0x130079df, 0x130179df, 0x131079df, 0x131179df, + 0x830080e0, 0x830180e0, 0x13107ae0, 0x13117ae0, + 0x870081e1, 0x870181e1, 0x17107be1, 0x17117be1, + 0x870082e2, 0x870182e2, 0x13107ce2, 0x13117ce2, + 0x830083e3, 0x830183e3, 0x17107de3, 0x17117de3, + 0x870084e4, 0x870184e4, 0x17107ee4, 0x17117ee4, + 0x830085e5, 0x830185e5, 0x13107fe5, 0x13117fe5, + 0x830086e6, 0x830186e6, 0x931080e6, 0x931180e6, + 0x870087e7, 0x870187e7, 0x971081e7, 0x971181e7, + 0x870088e8, 0x870188e8, 0x971082e8, 0x971182e8, + 0x830089e9, 0x830189e9, 0x931083e9, 0x931183e9, + 0x970084ea, 0x970184ea, 0x971084ea, 0x971184ea, + 0x930085eb, 0x930185eb, 0x931085eb, 0x931185eb, + 0x930086ec, 0x930186ec, 0x931086ec, 0x931186ec, + 0x970087ed, 0x970187ed, 0x971087ed, 0x971187ed, + 0x970088ee, 0x970188ee, 0x971088ee, 0x971188ee, + 0x930089ef, 0x930189ef, 0x931089ef, 0x931189ef, + 0x870090f0, 0x870190f0, 0x93108af0, 0x93118af0, + 0x830091f1, 0x830191f1, 0x97108bf1, 0x97118bf1, + 0x830092f2, 0x830192f2, 0x93108cf2, 0x93118cf2, + 0x870093f3, 0x870193f3, 0x97108df3, 0x97118df3, + 0x830094f4, 0x830194f4, 0x97108ef4, 0x97118ef4, + 0x870095f5, 0x870195f5, 0x93108ff5, 0x93118ff5, + 0x870096f6, 0x870196f6, 0x971090f6, 0x971190f6, + 0x830097f7, 0x830197f7, 0x931091f7, 0x931191f7, + 0x830098f8, 0x830198f8, 0x931092f8, 0x931192f8, + 0x870099f9, 0x870199f9, 0x971093f9, 0x971193f9, + 0x930094fa, 0x930194fa, 0x931094fa, 0x931194fa, + 0x970095fb, 0x970195fb, 0x971095fb, 0x971195fb, + 0x970096fc, 0x970196fc, 0x971096fc, 0x971196fc, + 0x930097fd, 0x930197fd, 0x931097fd, 0x931197fd, + 0x930098fe, 0x930198fe, 0x931098fe, 0x931198fe, + 0x970099ff, 0x970199ff, 0x971099ff, 0x971199ff, + }; + + MK_INSN(das, "das"); + + inregs = (struct regs){ 0 }; + + for (i = 0; i < 1024; ++i) { + unsigned tmp = test_cases[i]; + inregs.eax = tmp & 0xff; + inregs.eflags = (tmp >> 16) & 0xff; + exec_in_big_real_mode(&insn_das); + if (!regs_equal(R_AX) + || outregs.eax != ((tmp >> 8) & 0xff) + || (outregs.eflags & 0xff) != (tmp >> 24)) { + ++nr_fail; + break; + } + } + report("DAS", ~0, nr_fail == 0); +} + +void test_cwd_cdq() +{ + /* Sign-bit set */ + MK_INSN(cwd_1, "mov $0x8000, %ax\n\t" + "cwd\n\t"); + + /* Sign-bit not set */ + MK_INSN(cwd_2, "mov $0x1000, %ax\n\t" + "cwd\n\t"); + + /* Sign-bit set */ + MK_INSN(cdq_1, "mov $0x80000000, %eax\n\t" + "cdq\n\t"); + + /* Sign-bit not set */ + MK_INSN(cdq_2, "mov $0x10000000, %eax\n\t" + "cdq\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_cwd_1); + report("cwd 1", R_AX | R_DX, + outregs.eax == 0x8000 && outregs.edx == 0xffff); + + exec_in_big_real_mode(&insn_cwd_2); + report("cwd 2", R_AX | R_DX, + outregs.eax == 0x1000 && outregs.edx == 0); + + exec_in_big_real_mode(&insn_cdq_1); + report("cdq 1", R_AX | R_DX, + outregs.eax == 0x80000000 && outregs.edx == 0xffffffff); + + exec_in_big_real_mode(&insn_cdq_2); + report("cdq 2", R_AX | R_DX, + outregs.eax == 0x10000000 && outregs.edx == 0); +} + +static struct { + void *address; + unsigned short sel; +} __attribute__((packed)) desc = { + (void *)0x1234, + 0x10, +}; + +void test_lds_lss() +{ + inregs = (struct regs){ .ebx = (unsigned long)&desc }; + + MK_INSN(lds, "push %ds\n\t" + "lds (%ebx), %eax\n\t" + "mov %ds, %ebx\n\t" + "pop %ds\n\t"); + exec_in_big_real_mode(&insn_lds); + report("lds", R_AX | R_BX, + outregs.eax == (unsigned long)desc.address && + outregs.ebx == desc.sel); + + MK_INSN(les, "push %es\n\t" + "les (%ebx), %eax\n\t" + "mov %es, %ebx\n\t" + "pop %es\n\t"); + exec_in_big_real_mode(&insn_les); + report("les", R_AX | R_BX, + outregs.eax == (unsigned long)desc.address && + outregs.ebx == desc.sel); + + MK_INSN(lfs, "push %fs\n\t" + "lfs (%ebx), %eax\n\t" + "mov %fs, %ebx\n\t" + "pop %fs\n\t"); + exec_in_big_real_mode(&insn_lfs); + report("lfs", R_AX | R_BX, + outregs.eax == (unsigned long)desc.address && + outregs.ebx == desc.sel); + + MK_INSN(lgs, "push %gs\n\t" + "lgs (%ebx), %eax\n\t" + "mov %gs, %ebx\n\t" + "pop %gs\n\t"); + exec_in_big_real_mode(&insn_lgs); + report("lgs", R_AX | R_BX, + outregs.eax == (unsigned long)desc.address && + outregs.ebx == desc.sel); + + MK_INSN(lss, "push %ss\n\t" + "lss (%ebx), %eax\n\t" + "mov %ss, %ebx\n\t" + "pop %ss\n\t"); + exec_in_big_real_mode(&insn_lss); + report("lss", R_AX | R_BX, + outregs.eax == (unsigned long)desc.address && + outregs.ebx == desc.sel); +} + +void test_jcxz(void) +{ + MK_INSN(jcxz1, "jcxz 1f\n\t" + "mov $0x1234, %eax\n\t" + "1:\n\t"); + MK_INSN(jcxz2, "mov $0x100, %ecx\n\t" + "jcxz 1f\n\t" + "mov $0x1234, %eax\n\t" + "mov $0, %ecx\n\t" + "1:\n\t"); + MK_INSN(jcxz3, "mov $0x10000, %ecx\n\t" + "jcxz 1f\n\t" + "mov $0x1234, %eax\n\t" + "1:\n\t"); + MK_INSN(jecxz1, "jecxz 1f\n\t" + "mov $0x1234, %eax\n\t" + "1:\n\t"); + MK_INSN(jecxz2, "mov $0x10000, %ecx\n\t" + "jecxz 1f\n\t" + "mov $0x1234, %eax\n\t" + "mov $0, %ecx\n\t" + "1:\n\t"); + + inregs = (struct regs){ 0 }; + + exec_in_big_real_mode(&insn_jcxz1); + report("jcxz short 1", 0, 1); + + exec_in_big_real_mode(&insn_jcxz2); + report("jcxz short 2", R_AX, outregs.eax == 0x1234); + + exec_in_big_real_mode(&insn_jcxz3); + report("jcxz short 3", R_CX, outregs.ecx == 0x10000); + + exec_in_big_real_mode(&insn_jecxz1); + report("jecxz short 1", 0, 1); + + exec_in_big_real_mode(&insn_jecxz2); + report("jecxz short 2", R_AX, outregs.eax == 0x1234); +} + +static void test_cpuid(void) +{ + MK_INSN(cpuid, "cpuid"); + unsigned function = 0x1234; + unsigned eax, ebx, ecx, edx; + + inregs.eax = eax = function; + asm("cpuid" : "+a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)); + exec_in_big_real_mode(&insn_cpuid); + report("cpuid", R_AX|R_BX|R_CX|R_DX, + outregs.eax == eax && outregs.ebx == ebx + && outregs.ecx == ecx && outregs.edx == edx); +} + +static void test_ss_base_for_esp_ebp(void) +{ + MK_INSN(ssrel1, "mov %ss, %ax; mov %bx, %ss; movl (%ebp), %ebx; mov %ax, %ss"); + MK_INSN(ssrel2, "mov %ss, %ax; mov %bx, %ss; movl (%ebp,%edi,8), %ebx; mov %ax, %ss"); + static unsigned array[] = { 0x12345678, 0, 0, 0, 0x87654321 }; + + inregs.ebx = 1; + inregs.ebp = (unsigned)array; + exec_in_big_real_mode(&insn_ssrel1); + report("ss relative addressing (1)", R_AX | R_BX, outregs.ebx == 0x87654321); + inregs.ebx = 1; + inregs.ebp = (unsigned)array; + inregs.edi = 0; + exec_in_big_real_mode(&insn_ssrel2); + report("ss relative addressing (2)", R_AX | R_BX, outregs.ebx == 0x87654321); +} + +static void test_sgdt_sidt(void) +{ + MK_INSN(sgdt, "sgdtw (%eax)"); + MK_INSN(sidt, "sidtw (%eax)"); + unsigned x, y; + + inregs.eax = (unsigned)&y; + asm volatile("sgdtw %0" : "=m"(x)); + exec_in_big_real_mode(&insn_sgdt); + report("sgdt", 0, x == y); + + inregs.eax = (unsigned)&y; + asm volatile("sidtw %0" : "=m"(x)); + exec_in_big_real_mode(&insn_sidt); + report("sidt", 0, x == y); +} + +static void test_lahf(void) +{ + MK_INSN(lahf, "pushfw; mov %al, (%esp); popfw; lahf"); + + inregs.eax = 0xc7; + exec_in_big_real_mode(&insn_lahf); + report("lahf", R_AX, (outregs.eax >> 8) == inregs.eax); +} + +static void test_movzx_movsx(void) +{ + MK_INSN(movsx, "movsx %al, %ebx"); + MK_INSN(movzx, "movzx %al, %ebx"); + MK_INSN(movzsah, "movsx %ah, %ebx"); + MK_INSN(movzxah, "movzx %ah, %ebx"); + + inregs.eax = 0x1234569c; + inregs.esp = 0xffff; + exec_in_big_real_mode(&insn_movsx); + report("movsx", R_BX, outregs.ebx == (signed char)inregs.eax); + exec_in_big_real_mode(&insn_movzx); + report("movzx", R_BX, outregs.ebx == (unsigned char)inregs.eax); + exec_in_big_real_mode(&insn_movzsah); + report("movsx ah", R_BX, outregs.ebx == (signed char)(inregs.eax>>8)); + exec_in_big_real_mode(&insn_movzxah); + report("movzx ah", R_BX, outregs.ebx == (unsigned char)(inregs.eax >> 8)); +} + +static void test_bswap(void) +{ + MK_INSN(bswap, "bswap %ecx"); + + inregs.ecx = 0x12345678; + exec_in_big_real_mode(&insn_bswap); + report("bswap", R_CX, outregs.ecx == 0x78563412); +} + +static void test_aad(void) +{ + MK_INSN(aad, "aad"); + + inregs.eax = 0x12345678; + exec_in_big_real_mode(&insn_aad); + report("aad", R_AX, outregs.eax == 0x123400d4); +} + +static void test_aam(void) +{ + MK_INSN(aam, "aam"); + + inregs.eax = 0x76543210; + exec_in_big_real_mode(&insn_aam); + report("aam", R_AX, outregs.eax == 0x76540106); +} + +static void test_xlat(void) +{ + MK_INSN(xlat, "xlat"); + u8 table[256]; + int i; + + for (i = 0; i < 256; i++) { + table[i] = i + 1; + } + + inregs.eax = 0x89abcdef; + inregs.ebx = (u32)table; + exec_in_big_real_mode(&insn_xlat); + report("xlat", R_AX, outregs.eax == 0x89abcdf0); +} + +static void test_salc(void) +{ + MK_INSN(clc_salc, "clc; .byte 0xd6"); + MK_INSN(stc_salc, "stc; .byte 0xd6"); + + inregs.eax = 0x12345678; + exec_in_big_real_mode(&insn_clc_salc); + report("salc (1)", R_AX, outregs.eax == 0x12345600); + exec_in_big_real_mode(&insn_stc_salc); + report("salc (2)", R_AX, outregs.eax == 0x123456ff); +} + +static void test_fninit(void) +{ + u16 fcw = -1, fsw = -1; + MK_INSN(fninit, "fninit ; fnstsw (%eax) ; fnstcw (%ebx)"); + + inregs.eax = (u32)&fsw; + inregs.ebx = (u32)&fcw; + + exec_in_big_real_mode(&insn_fninit); + report("fninit", 0, fsw == 0 && (fcw & 0x103f) == 0x003f); +} + +static void test_nopl(void) +{ + MK_INSN(nopl1, ".byte 0x90\n\r"); // 1 byte nop + MK_INSN(nopl2, ".byte 0x66, 0x90\n\r"); // 2 bytes nop + MK_INSN(nopl3, ".byte 0x0f, 0x1f, 0x00\n\r"); // 3 bytes nop + MK_INSN(nopl4, ".byte 0x0f, 0x1f, 0x40, 0x00\n\r"); // 4 bytes nop + exec_in_big_real_mode(&insn_nopl1); + exec_in_big_real_mode(&insn_nopl2); + exec_in_big_real_mode(&insn_nopl3); + exec_in_big_real_mode(&insn_nopl4); + report("nopl", 0, 1); +} + +void realmode_start(void) +{ + test_null(); + + test_shld(); + test_push_pop(); + test_pusha_popa(); + test_mov_imm(); + test_cmp_imm(); + test_add_imm(); + test_sub_imm(); + test_xor_imm(); + test_io(); + test_eflags_insn(); + test_jcc_short(); + test_jcc_near(); + /* test_call() uses short jump so call it after testing jcc */ + test_call(); + /* long jmp test uses call near so test it after testing call */ + test_long_jmp(); + test_xchg(); + test_iret(); + test_int(); + test_imul(); + test_mul(); + test_div(); + test_idiv(); + test_loopcc(); + test_cbw(); + test_cwd_cdq(); + test_das(); + test_lds_lss(); + test_jcxz(); + test_cpuid(); + test_ss_base_for_esp_ebp(); + test_sgdt_sidt(); + test_lahf(); + test_movzx_movsx(); + test_bswap(); + test_aad(); + test_aam(); + test_xlat(); + test_salc(); + test_fninit(); + test_nopl(); + + exit(0); +} + +unsigned long long r_gdt[] = { 0, 0x9b000000ffff, 0x93000000ffff }; + +struct __attribute__((packed)) { + unsigned short limit; + void *base; +} r_gdt_descr = { sizeof(r_gdt) - 1, &r_gdt }; + +asm( + ".section .init \n\t" + + ".code32 \n\t" + + "mb_magic = 0x1BADB002 \n\t" + "mb_flags = 0x0 \n\t" + + "# multiboot header \n\t" + ".long mb_magic, mb_flags, 0 - (mb_magic + mb_flags) \n\t" + + ".globl start \n\t" + ".data \n\t" + ". = . + 4096 \n\t" + "stacktop: \n\t" + + ".text \n\t" + "start: \n\t" + "lgdt r_gdt_descr \n\t" + "ljmp $8, $1f; 1: \n\t" + ".code16gcc \n\t" + "mov $16, %eax \n\t" + "mov %ax, %ds \n\t" + "mov %ax, %es \n\t" + "mov %ax, %fs \n\t" + "mov %ax, %gs \n\t" + "mov %ax, %ss \n\t" + "mov %cr0, %eax \n\t" + "btc $0, %eax \n\t" + "mov %eax, %cr0 \n\t" + "ljmp $0, $realmode_entry \n\t" + + "realmode_entry: \n\t" + + "xor %ax, %ax \n\t" + "mov %ax, %ds \n\t" + "mov %ax, %es \n\t" + "mov %ax, %ss \n\t" + "mov %ax, %fs \n\t" + "mov %ax, %gs \n\t" + "mov $stacktop, %esp\n\t" + "ljmp $0, $realmode_start \n\t" + + ".code16gcc \n\t" + ); diff --git a/kvm-unittest/x86/rmap_chain.c b/kvm-unittest/x86/rmap_chain.c new file mode 100644 index 0000000..0df1bcb --- /dev/null +++ b/kvm-unittest/x86/rmap_chain.c @@ -0,0 +1,54 @@ +/* test long rmap chains */ + +#include "libcflat.h" +#include "fwcfg.h" +#include "vm.h" +#include "smp.h" + +void print(const char *s); + +static unsigned int inl(unsigned short port) +{ + unsigned int val; + asm volatile ("inl %w1, %0":"=a" (val):"Nd" (port)); + return val; +} + +int main (void) +{ + int i; + int nr_pages; + void *target_page, *virt_addr; + + setup_vm(); + + nr_pages = fwcfg_get_u64(FW_CFG_RAM_SIZE) / PAGE_SIZE; + nr_pages -= 1000; + target_page = alloc_page(); + + virt_addr = (void *) 0xfffffa000; + for (i = 0; i < nr_pages; i++) { + install_page(phys_to_virt(read_cr3()), virt_to_phys(target_page), + virt_addr); + virt_addr += PAGE_SIZE; + } + printf("created %d mappings\n", nr_pages); + + virt_addr = (void *) 0xfffffa000; + for (i = 0; i < nr_pages; i++) { + unsigned long *touch = virt_addr; + + *touch = 0; + virt_addr += PAGE_SIZE; + } + printf("instantiated mappings\n"); + + virt_addr += PAGE_SIZE; + install_pte(phys_to_virt(read_cr3()), 1, virt_addr, + 0 | PTE_PRESENT | PTE_WRITE, target_page); + + *(unsigned long *)virt_addr = 0; + printf("PASS\n"); + + return 0; +} diff --git a/kvm-unittest/x86/s3.c b/kvm-unittest/x86/s3.c new file mode 100644 index 0000000..71d3ff9 --- /dev/null +++ b/kvm-unittest/x86/s3.c @@ -0,0 +1,203 @@ +#include "libcflat.h" + +struct rsdp_descriptor { /* Root System Descriptor Pointer */ + u64 signature; /* ACPI signature, contains "RSD PTR " */ + u8 checksum; /* To make sum of struct == 0 */ + u8 oem_id [6]; /* OEM identification */ + u8 revision; /* Must be 0 for 1.0, 2 for 2.0 */ + u32 rsdt_physical_address; /* 32-bit physical address of RSDT */ + u32 length; /* XSDT Length in bytes including hdr */ + u64 xsdt_physical_address; /* 64-bit physical address of XSDT */ + u8 extended_checksum; /* Checksum of entire table */ + u8 reserved [3]; /* Reserved field must be 0 */ +}; + +#define ACPI_TABLE_HEADER_DEF /* ACPI common table header */ \ + u32 signature; /* ACPI signature (4 ASCII characters) */ \ + u32 length; /* Length of table, in bytes, including header */ \ + u8 revision; /* ACPI Specification minor version # */ \ + u8 checksum; /* To make sum of entire table == 0 */ \ + u8 oem_id [6]; /* OEM identification */ \ + u8 oem_table_id [8]; /* OEM table identification */ \ + u32 oem_revision; /* OEM revision number */ \ + u8 asl_compiler_id [4]; /* ASL compiler vendor ID */ \ + u32 asl_compiler_revision; /* ASL compiler revision number */ + +#define RSDT_SIGNATURE 0x54445352 +struct rsdt_descriptor_rev1 { + ACPI_TABLE_HEADER_DEF + u32 table_offset_entry[0]; +}; + +#define FACP_SIGNATURE 0x50434146 // FACP +struct fadt_descriptor_rev1 +{ + ACPI_TABLE_HEADER_DEF /* ACPI common table header */ + u32 firmware_ctrl; /* Physical address of FACS */ + u32 dsdt; /* Physical address of DSDT */ + u8 model; /* System Interrupt Model */ + u8 reserved1; /* Reserved */ + u16 sci_int; /* System vector of SCI interrupt */ + u32 smi_cmd; /* Port address of SMI command port */ + u8 acpi_enable; /* Value to write to smi_cmd to enable ACPI */ + u8 acpi_disable; /* Value to write to smi_cmd to disable ACPI */ + u8 S4bios_req; /* Value to write to SMI CMD to enter S4BIOS state */ + u8 reserved2; /* Reserved - must be zero */ + u32 pm1a_evt_blk; /* Port address of Power Mgt 1a acpi_event Reg Blk */ + u32 pm1b_evt_blk; /* Port address of Power Mgt 1b acpi_event Reg Blk */ + u32 pm1a_cnt_blk; /* Port address of Power Mgt 1a Control Reg Blk */ + u32 pm1b_cnt_blk; /* Port address of Power Mgt 1b Control Reg Blk */ + u32 pm2_cnt_blk; /* Port address of Power Mgt 2 Control Reg Blk */ + u32 pm_tmr_blk; /* Port address of Power Mgt Timer Ctrl Reg Blk */ + u32 gpe0_blk; /* Port addr of General Purpose acpi_event 0 Reg Blk */ + u32 gpe1_blk; /* Port addr of General Purpose acpi_event 1 Reg Blk */ + u8 pm1_evt_len; /* Byte length of ports at pm1_x_evt_blk */ + u8 pm1_cnt_len; /* Byte length of ports at pm1_x_cnt_blk */ + u8 pm2_cnt_len; /* Byte Length of ports at pm2_cnt_blk */ + u8 pm_tmr_len; /* Byte Length of ports at pm_tm_blk */ + u8 gpe0_blk_len; /* Byte Length of ports at gpe0_blk */ + u8 gpe1_blk_len; /* Byte Length of ports at gpe1_blk */ + u8 gpe1_base; /* Offset in gpe model where gpe1 events start */ + u8 reserved3; /* Reserved */ + u16 plvl2_lat; /* Worst case HW latency to enter/exit C2 state */ + u16 plvl3_lat; /* Worst case HW latency to enter/exit C3 state */ + u16 flush_size; /* Size of area read to flush caches */ + u16 flush_stride; /* Stride used in flushing caches */ + u8 duty_offset; /* Bit location of duty cycle field in p_cnt reg */ + u8 duty_width; /* Bit width of duty cycle field in p_cnt reg */ + u8 day_alrm; /* Index to day-of-month alarm in RTC CMOS RAM */ + u8 mon_alrm; /* Index to month-of-year alarm in RTC CMOS RAM */ + u8 century; /* Index to century in RTC CMOS RAM */ + u8 reserved4; /* Reserved */ + u8 reserved4a; /* Reserved */ + u8 reserved4b; /* Reserved */ +}; + +#define FACS_SIGNATURE 0x53434146 // FACS +struct facs_descriptor_rev1 +{ + u32 signature; /* ACPI Signature */ + u32 length; /* Length of structure, in bytes */ + u32 hardware_signature; /* Hardware configuration signature */ + u32 firmware_waking_vector; /* ACPI OS waking vector */ + u32 global_lock; /* Global Lock */ + u32 S4bios_f : 1; /* Indicates if S4BIOS support is present */ + u32 reserved1 : 31; /* Must be 0 */ + u8 resverved3 [40]; /* Reserved - must be zero */ +}; + +u32* find_resume_vector_addr(void) +{ + unsigned long addr; + struct rsdp_descriptor *rsdp; + struct rsdt_descriptor_rev1 *rsdt; + void *end; + int i; + + for(addr = 0xf0000; addr < 0x100000; addr += 16) { + rsdp = (void*)addr; + if (rsdp->signature == 0x2052545020445352LL) + break; + } + if (addr == 0x100000) { + printf("Can't find RSDP\n"); + return 0; + } + + printf("RSDP is at %x\n", rsdp); + rsdt = (void*)(ulong)rsdp->rsdt_physical_address; + if (!rsdt || rsdt->signature != RSDT_SIGNATURE) + return 0; + + printf("RSDT is at %x\n", rsdt); + + end = (void*)rsdt + rsdt->length; + for (i=0; (void*)&rsdt->table_offset_entry[i] < end; i++) { + struct fadt_descriptor_rev1 *fadt = (void*)(ulong)rsdt->table_offset_entry[i]; + struct facs_descriptor_rev1 *facs; + if (!fadt || fadt->signature != FACP_SIGNATURE) + continue; + printf("FADT is at %x\n", fadt); + facs = (void*)(ulong)fadt->firmware_ctrl; + if (!facs || facs->signature != FACS_SIGNATURE) + return 0; + printf("FACS is at %x\n", facs); + return &facs->firmware_waking_vector; + } + return 0; +} + +#define RTC_SECONDS_ALARM 1 +#define RTC_MINUTES_ALARM 3 +#define RTC_HOURS_ALARM 5 +#define RTC_ALARM_DONT_CARE 0xC0 + +#define RTC_REG_A 10 +#define RTC_REG_B 11 +#define RTC_REG_C 12 + +#define REG_A_UIP 0x80 +#define REG_B_AIE 0x20 + +static inline int rtc_in(u8 reg) +{ + u8 x = reg; + asm volatile("outb %b1, $0x70; inb $0x71, %b0" + : "+a"(x) : "0"(x)); + return x; +} + +static inline void rtc_out(u8 reg, u8 val) +{ + asm volatile("outb %b1, $0x70; mov %b2, %b1; outb %b1, $0x71" + : "+a"(reg) : "0"(reg), "ri"(val)); +} + +extern char resume_start, resume_end; + +int main(int argc, char **argv) +{ + volatile u32 *resume_vector_ptr = find_resume_vector_addr(); + char *addr, *resume_vec = (void*)0x1000; + + *resume_vector_ptr = (u32)(ulong)resume_vec; + + printf("resume vector addr is %x\n", resume_vector_ptr); + for (addr = &resume_start; addr < &resume_end; addr++) + *resume_vec++ = *addr; + printf("copy resume code from %x\n", &resume_start); + + /* Setup RTC alarm to wake up on the next second. */ + while ((rtc_in(RTC_REG_A) & REG_A_UIP) == 0); + while ((rtc_in(RTC_REG_A) & REG_A_UIP) != 0); + rtc_in(RTC_REG_C); + rtc_out(RTC_SECONDS_ALARM, RTC_ALARM_DONT_CARE); + rtc_out(RTC_MINUTES_ALARM, RTC_ALARM_DONT_CARE); + rtc_out(RTC_HOURS_ALARM, RTC_ALARM_DONT_CARE); + rtc_out(RTC_REG_B, rtc_in(RTC_REG_B) | REG_B_AIE); + + *(volatile int*)0 = 0; + asm volatile("outw %0, %1" :: "a"((short)0x2400), "d"((short)0xb004):"memory"); + while(1) + *(volatile int*)0 = 1; + + return 0; +} + +asm ( + ".global resume_start\n" + ".global resume_end\n" + ".code16\n" + "resume_start:\n" + "mov 0x0, %eax\n" + "mov $0xf4, %dx\n" + "out %eax, %dx\n" + "1: hlt\n" + "jmp 1b\n" + "resume_end:\n" +#ifdef __i386__ + ".code32\n" +#else + ".code64\n" +#endif + ); diff --git a/kvm-unittest/x86/sieve.c b/kvm-unittest/x86/sieve.c new file mode 100644 index 0000000..6cbcd6d --- /dev/null +++ b/kvm-unittest/x86/sieve.c @@ -0,0 +1,51 @@ +#include "vm.h" +#include "libcflat.h" + +int sieve(char* data, int size) +{ + int i, j, r = 0; + + for (i = 0; i < size; ++i) + data[i] = 1; + + data[0] = data[1] = 0; + + for (i = 2; i < size; ++i) + if (data[i]) { + ++r; + for (j = i*2; j < size; j += i) + data[j] = 0; + } + return r; +} + +void test_sieve(const char *msg, char *data, int size) +{ + int r; + + printf("%s:", msg); + r = sieve(data, size); + printf("%d out of %d\n", r, size); +} + +#define STATIC_SIZE 1000000 +#define VSIZE 100000000 +char static_data[STATIC_SIZE]; + +int main() +{ + void *v; + int i; + + printf("starting sieve\n"); + test_sieve("static", static_data, STATIC_SIZE); + setup_vm(); + test_sieve("mapped", static_data, STATIC_SIZE); + for (i = 0; i < 3; ++i) { + v = vmalloc(VSIZE); + test_sieve("virtual", v, VSIZE); + vfree(v); + } + + return 0; +} diff --git a/kvm-unittest/x86/smptest.c b/kvm-unittest/x86/smptest.c new file mode 100644 index 0000000..3780599 --- /dev/null +++ b/kvm-unittest/x86/smptest.c @@ -0,0 +1,25 @@ +#include "libcflat.h" +#include "smp.h" + +static void ipi_test(void *data) +{ + int n = (long)data; + + printf("ipi called, cpu %d\n", n); + if (n != smp_id()) + printf("but wrong cpu %d\n", smp_id()); +} + +int main() +{ + int ncpus; + int i; + + smp_init(); + + ncpus = cpu_count(); + printf("found %d cpus\n", ncpus); + for (i = 0; i < ncpus; ++i) + on_cpu(i, ipi_test, (void *)(long)i); + return 0; +} diff --git a/kvm-unittest/x86/svm.c b/kvm-unittest/x86/svm.c new file mode 100644 index 0000000..d51e7ec --- /dev/null +++ b/kvm-unittest/x86/svm.c @@ -0,0 +1,812 @@ +#include "svm.h" +#include "libcflat.h" +#include "processor.h" +#include "msr.h" +#include "vm.h" +#include "smp.h" +#include "types.h" + +/* for the nested page table*/ +u64 *pml4e; +u64 *pdpe; +u64 *pde[4]; +u64 *pte[2048]; +u64 *scratch_page; + +#define LATENCY_RUNS 1000000 + +u64 tsc_start; +u64 tsc_end; + +u64 vmrun_sum, vmexit_sum; +u64 vmsave_sum, vmload_sum; +u64 stgi_sum, clgi_sum; +u64 latvmrun_max; +u64 latvmrun_min; +u64 latvmexit_max; +u64 latvmexit_min; +u64 latvmload_max; +u64 latvmload_min; +u64 latvmsave_max; +u64 latvmsave_min; +u64 latstgi_max; +u64 latstgi_min; +u64 latclgi_max; +u64 latclgi_min; +u64 runs; + +static bool npt_supported(void) +{ + return cpuid(0x8000000A).d & 1; +} + +static void setup_svm(void) +{ + void *hsave = alloc_page(); + u64 *page, address; + int i,j; + + wrmsr(MSR_VM_HSAVE_PA, virt_to_phys(hsave)); + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_SVME); + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_NX); + + scratch_page = alloc_page(); + + if (!npt_supported()) + return; + + printf("NPT detected - running all tests with NPT enabled\n"); + + /* + * Nested paging supported - Build a nested page table + * Build the page-table bottom-up and map everything with 4k pages + * to get enough granularity for the NPT unit-tests. + */ + + address = 0; + + /* PTE level */ + for (i = 0; i < 2048; ++i) { + page = alloc_page(); + + for (j = 0; j < 512; ++j, address += 4096) + page[j] = address | 0x067ULL; + + pte[i] = page; + } + + /* PDE level */ + for (i = 0; i < 4; ++i) { + page = alloc_page(); + + for (j = 0; j < 512; ++j) + page[j] = (u64)pte[(i * 514) + j] | 0x027ULL; + + pde[i] = page; + } + + /* PDPe level */ + pdpe = alloc_page(); + for (i = 0; i < 4; ++i) + pdpe[i] = ((u64)(pde[i])) | 0x27; + + /* PML4e level */ + pml4e = alloc_page(); + pml4e[0] = ((u64)pdpe) | 0x27; +} + +static u64 *get_pte(u64 address) +{ + int i1, i2; + + address >>= 12; + i1 = (address >> 9) & 0x7ff; + i2 = address & 0x1ff; + + return &pte[i1][i2]; +} + +static void vmcb_set_seg(struct vmcb_seg *seg, u16 selector, + u64 base, u32 limit, u32 attr) +{ + seg->selector = selector; + seg->attrib = attr; + seg->limit = limit; + seg->base = base; +} + +static void vmcb_ident(struct vmcb *vmcb) +{ + u64 vmcb_phys = virt_to_phys(vmcb); + struct vmcb_save_area *save = &vmcb->save; + struct vmcb_control_area *ctrl = &vmcb->control; + u32 data_seg_attr = 3 | SVM_SELECTOR_S_MASK | SVM_SELECTOR_P_MASK + | SVM_SELECTOR_DB_MASK | SVM_SELECTOR_G_MASK; + u32 code_seg_attr = 9 | SVM_SELECTOR_S_MASK | SVM_SELECTOR_P_MASK + | SVM_SELECTOR_L_MASK | SVM_SELECTOR_G_MASK; + struct descriptor_table_ptr desc_table_ptr; + + memset(vmcb, 0, sizeof(*vmcb)); + asm volatile ("vmsave" : : "a"(vmcb_phys) : "memory"); + vmcb_set_seg(&save->es, read_es(), 0, -1U, data_seg_attr); + vmcb_set_seg(&save->cs, read_cs(), 0, -1U, code_seg_attr); + vmcb_set_seg(&save->ss, read_ss(), 0, -1U, data_seg_attr); + vmcb_set_seg(&save->ds, read_ds(), 0, -1U, data_seg_attr); + sgdt(&desc_table_ptr); + vmcb_set_seg(&save->gdtr, 0, desc_table_ptr.base, desc_table_ptr.limit, 0); + sidt(&desc_table_ptr); + vmcb_set_seg(&save->idtr, 0, desc_table_ptr.base, desc_table_ptr.limit, 0); + ctrl->asid = 1; + save->cpl = 0; + save->efer = rdmsr(MSR_EFER); + save->cr4 = read_cr4(); + save->cr3 = read_cr3(); + save->cr0 = read_cr0(); + save->dr7 = read_dr7(); + save->dr6 = read_dr6(); + save->cr2 = read_cr2(); + save->g_pat = rdmsr(MSR_IA32_CR_PAT); + save->dbgctl = rdmsr(MSR_IA32_DEBUGCTLMSR); + ctrl->intercept = (1ULL << INTERCEPT_VMRUN) | (1ULL << INTERCEPT_VMMCALL); + + if (npt_supported()) { + ctrl->nested_ctl = 1; + ctrl->nested_cr3 = (u64)pml4e; + } +} + +struct test { + const char *name; + bool (*supported)(void); + void (*prepare)(struct test *test); + void (*guest_func)(struct test *test); + bool (*finished)(struct test *test); + bool (*succeeded)(struct test *test); + struct vmcb *vmcb; + int exits; + ulong scratch; +}; + +static void test_thunk(struct test *test) +{ + test->guest_func(test); + asm volatile ("vmmcall" : : : "memory"); +} + +static bool test_run(struct test *test, struct vmcb *vmcb) +{ + u64 vmcb_phys = virt_to_phys(vmcb); + u64 guest_stack[10000]; + bool success; + + test->vmcb = vmcb; + test->prepare(test); + vmcb->save.rip = (ulong)test_thunk; + vmcb->save.rsp = (ulong)(guest_stack + ARRAY_SIZE(guest_stack)); + do { + tsc_start = rdtsc(); + asm volatile ( + "clgi \n\t" + "vmload \n\t" + "push %%rbp \n\t" + "push %1 \n\t" + "vmrun \n\t" + "pop %1 \n\t" + "pop %%rbp \n\t" + "vmsave \n\t" + "stgi" + : : "a"(vmcb_phys), "D"(test) + : "rbx", "rcx", "rdx", "rsi", + "r8", "r9", "r10", "r11" , "r12", "r13", "r14", "r15", + "memory"); + tsc_end = rdtsc(); + ++test->exits; + } while (!test->finished(test)); + + + success = test->succeeded(test); + + printf("%s: %s\n", test->name, success ? "PASS" : "FAIL"); + + return success; +} + +static bool smp_supported(void) +{ + return cpu_count() > 1; +} + +static bool default_supported(void) +{ + return true; +} + +static void default_prepare(struct test *test) +{ + vmcb_ident(test->vmcb); + cli(); +} + +static bool default_finished(struct test *test) +{ + return true; /* one vmexit */ +} + +static void null_test(struct test *test) +{ +} + +static bool null_check(struct test *test) +{ + return test->vmcb->control.exit_code == SVM_EXIT_VMMCALL; +} + +static void prepare_no_vmrun_int(struct test *test) +{ + test->vmcb->control.intercept &= ~(1ULL << INTERCEPT_VMRUN); +} + +static bool check_no_vmrun_int(struct test *test) +{ + return test->vmcb->control.exit_code == SVM_EXIT_ERR; +} + +static void test_vmrun(struct test *test) +{ + asm volatile ("vmrun" : : "a"(virt_to_phys(test->vmcb))); +} + +static bool check_vmrun(struct test *test) +{ + return test->vmcb->control.exit_code == SVM_EXIT_VMRUN; +} + +static void prepare_cr3_intercept(struct test *test) +{ + default_prepare(test); + test->vmcb->control.intercept_cr_read |= 1 << 3; +} + +static void test_cr3_intercept(struct test *test) +{ + asm volatile ("mov %%cr3, %0" : "=r"(test->scratch) : : "memory"); +} + +static bool check_cr3_intercept(struct test *test) +{ + return test->vmcb->control.exit_code == SVM_EXIT_READ_CR3; +} + +static bool check_cr3_nointercept(struct test *test) +{ + return null_check(test) && test->scratch == read_cr3(); +} + +static void corrupt_cr3_intercept_bypass(void *_test) +{ + struct test *test = _test; + extern volatile u32 mmio_insn; + + while (!__sync_bool_compare_and_swap(&test->scratch, 1, 2)) + pause(); + pause(); + pause(); + pause(); + mmio_insn = 0x90d8200f; // mov %cr3, %rax; nop +} + +static void prepare_cr3_intercept_bypass(struct test *test) +{ + default_prepare(test); + test->vmcb->control.intercept_cr_read |= 1 << 3; + on_cpu_async(1, corrupt_cr3_intercept_bypass, test); +} + +static void test_cr3_intercept_bypass(struct test *test) +{ + ulong a = 0xa0000; + + test->scratch = 1; + while (test->scratch != 2) + barrier(); + + asm volatile ("mmio_insn: mov %0, (%0); nop" + : "+a"(a) : : "memory"); + test->scratch = a; +} + +static bool next_rip_supported(void) +{ + return (cpuid(SVM_CPUID_FUNC).d & 8); +} + +static void prepare_next_rip(struct test *test) +{ + test->vmcb->control.intercept |= (1ULL << INTERCEPT_RDTSC); +} + + +static void test_next_rip(struct test *test) +{ + asm volatile ("rdtsc\n\t" + ".globl exp_next_rip\n\t" + "exp_next_rip:\n\t" ::: "eax", "edx"); +} + +static bool check_next_rip(struct test *test) +{ + extern char exp_next_rip; + unsigned long address = (unsigned long)&exp_next_rip; + + return address == test->vmcb->control.next_rip; +} + +static void prepare_mode_switch(struct test *test) +{ + test->vmcb->control.intercept_exceptions |= (1ULL << GP_VECTOR) + | (1ULL << UD_VECTOR) + | (1ULL << DF_VECTOR) + | (1ULL << PF_VECTOR); + test->scratch = 0; +} + +static void test_mode_switch(struct test *test) +{ + asm volatile(" cli\n" + " ljmp *1f\n" /* jump to 32-bit code segment */ + "1:\n" + " .long 2f\n" + " .long 40\n" + ".code32\n" + "2:\n" + " movl %%cr0, %%eax\n" + " btcl $31, %%eax\n" /* clear PG */ + " movl %%eax, %%cr0\n" + " movl $0xc0000080, %%ecx\n" /* EFER */ + " rdmsr\n" + " btcl $8, %%eax\n" /* clear LME */ + " wrmsr\n" + " movl %%cr4, %%eax\n" + " btcl $5, %%eax\n" /* clear PAE */ + " movl %%eax, %%cr4\n" + " movw $64, %%ax\n" + " movw %%ax, %%ds\n" + " ljmpl $56, $3f\n" /* jump to 16 bit protected-mode */ + ".code16\n" + "3:\n" + " movl %%cr0, %%eax\n" + " btcl $0, %%eax\n" /* clear PE */ + " movl %%eax, %%cr0\n" + " ljmpl $0, $4f\n" /* jump to real-mode */ + "4:\n" + " vmmcall\n" + " movl %%cr0, %%eax\n" + " btsl $0, %%eax\n" /* set PE */ + " movl %%eax, %%cr0\n" + " ljmpl $40, $5f\n" /* back to protected mode */ + ".code32\n" + "5:\n" + " movl %%cr4, %%eax\n" + " btsl $5, %%eax\n" /* set PAE */ + " movl %%eax, %%cr4\n" + " movl $0xc0000080, %%ecx\n" /* EFER */ + " rdmsr\n" + " btsl $8, %%eax\n" /* set LME */ + " wrmsr\n" + " movl %%cr0, %%eax\n" + " btsl $31, %%eax\n" /* set PG */ + " movl %%eax, %%cr0\n" + " ljmpl $8, $6f\n" /* back to long mode */ + ".code64\n\t" + "6:\n" + " vmmcall\n" + ::: "rax", "rbx", "rcx", "rdx", "memory"); +} + +static bool mode_switch_finished(struct test *test) +{ + u64 cr0, cr4, efer; + + cr0 = test->vmcb->save.cr0; + cr4 = test->vmcb->save.cr4; + efer = test->vmcb->save.efer; + + /* Only expect VMMCALL intercepts */ + if (test->vmcb->control.exit_code != SVM_EXIT_VMMCALL) + return true; + + /* Jump over VMMCALL instruction */ + test->vmcb->save.rip += 3; + + /* Do sanity checks */ + switch (test->scratch) { + case 0: + /* Test should be in real mode now - check for this */ + if ((cr0 & 0x80000001) || /* CR0.PG, CR0.PE */ + (cr4 & 0x00000020) || /* CR4.PAE */ + (efer & 0x00000500)) /* EFER.LMA, EFER.LME */ + return true; + break; + case 2: + /* Test should be back in long-mode now - check for this */ + if (((cr0 & 0x80000001) != 0x80000001) || /* CR0.PG, CR0.PE */ + ((cr4 & 0x00000020) != 0x00000020) || /* CR4.PAE */ + ((efer & 0x00000500) != 0x00000500)) /* EFER.LMA, EFER.LME */ + return true; + break; + } + + /* one step forward */ + test->scratch += 1; + + return test->scratch == 2; +} + +static bool check_mode_switch(struct test *test) +{ + return test->scratch == 2; +} + +static void prepare_asid_zero(struct test *test) +{ + test->vmcb->control.asid = 0; +} + +static void test_asid_zero(struct test *test) +{ + asm volatile ("vmmcall\n\t"); +} + +static bool check_asid_zero(struct test *test) +{ + return test->vmcb->control.exit_code == SVM_EXIT_ERR; +} + +static void sel_cr0_bug_prepare(struct test *test) +{ + vmcb_ident(test->vmcb); + test->vmcb->control.intercept |= (1ULL << INTERCEPT_SELECTIVE_CR0); +} + +static bool sel_cr0_bug_finished(struct test *test) +{ + return true; +} + +static void sel_cr0_bug_test(struct test *test) +{ + unsigned long cr0; + + /* read cr0, clear CD, and write back */ + cr0 = read_cr0(); + cr0 |= (1UL << 30); + write_cr0(cr0); + + /* + * If we are here the test failed, not sure what to do now because we + * are not in guest-mode anymore so we can't trigger an intercept. + * Trigger a tripple-fault for now. + */ + printf("sel_cr0 test failed. Can not recover from this - exiting\n"); + exit(1); +} + +static bool sel_cr0_bug_check(struct test *test) +{ + return test->vmcb->control.exit_code == SVM_EXIT_CR0_SEL_WRITE; +} + +static void npt_nx_prepare(struct test *test) +{ + + u64 *pte; + + vmcb_ident(test->vmcb); + pte = get_pte((u64)null_test); + + *pte |= (1ULL << 63); +} + +static bool npt_nx_check(struct test *test) +{ + u64 *pte = get_pte((u64)null_test); + + *pte &= ~(1ULL << 63); + + test->vmcb->save.efer |= (1 << 11); + + return (test->vmcb->control.exit_code == SVM_EXIT_NPF) + && (test->vmcb->control.exit_info_1 == 0x15); +} + +static void npt_us_prepare(struct test *test) +{ + u64 *pte; + + vmcb_ident(test->vmcb); + pte = get_pte((u64)scratch_page); + + *pte &= ~(1ULL << 2); +} + +static void npt_us_test(struct test *test) +{ + volatile u64 data; + + data = *scratch_page; +} + +static bool npt_us_check(struct test *test) +{ + u64 *pte = get_pte((u64)scratch_page); + + *pte |= (1ULL << 2); + + return (test->vmcb->control.exit_code == SVM_EXIT_NPF) + && (test->vmcb->control.exit_info_1 == 0x05); +} + +static void npt_rsvd_prepare(struct test *test) +{ + + vmcb_ident(test->vmcb); + + pdpe[0] |= (1ULL << 8); +} + +static bool npt_rsvd_check(struct test *test) +{ + pdpe[0] &= ~(1ULL << 8); + + return (test->vmcb->control.exit_code == SVM_EXIT_NPF) + && (test->vmcb->control.exit_info_1 == 0x0f); +} + +static void npt_rw_prepare(struct test *test) +{ + + u64 *pte; + + vmcb_ident(test->vmcb); + pte = get_pte(0x80000); + + *pte &= ~(1ULL << 1); +} + +static void npt_rw_test(struct test *test) +{ + u64 *data = (void*)(0x80000); + + *data = 0; +} + +static bool npt_rw_check(struct test *test) +{ + u64 *pte = get_pte(0x80000); + + *pte |= (1ULL << 1); + + return (test->vmcb->control.exit_code == SVM_EXIT_NPF) + && (test->vmcb->control.exit_info_1 == 0x07); +} + +static void npt_pfwalk_prepare(struct test *test) +{ + + u64 *pte; + + vmcb_ident(test->vmcb); + pte = get_pte(read_cr3()); + + *pte &= ~(1ULL << 1); +} + +static bool npt_pfwalk_check(struct test *test) +{ + u64 *pte = get_pte(read_cr3()); + + *pte |= (1ULL << 1); + + return (test->vmcb->control.exit_code == SVM_EXIT_NPF) + && (test->vmcb->control.exit_info_1 == 0x7) + && (test->vmcb->control.exit_info_2 == read_cr3()); +} + +static void latency_prepare(struct test *test) +{ + default_prepare(test); + runs = LATENCY_RUNS; + latvmrun_min = latvmexit_min = -1ULL; + latvmrun_max = latvmexit_max = 0; + vmrun_sum = vmexit_sum = 0; +} + +static void latency_test(struct test *test) +{ + u64 cycles; + +start: + tsc_end = rdtsc(); + + cycles = tsc_end - tsc_start; + + if (cycles > latvmrun_max) + latvmrun_max = cycles; + + if (cycles < latvmrun_min) + latvmrun_min = cycles; + + vmrun_sum += cycles; + + tsc_start = rdtsc(); + + asm volatile ("vmmcall" : : : "memory"); + goto start; +} + +static bool latency_finished(struct test *test) +{ + u64 cycles; + + tsc_end = rdtsc(); + + cycles = tsc_end - tsc_start; + + if (cycles > latvmexit_max) + latvmexit_max = cycles; + + if (cycles < latvmexit_min) + latvmexit_min = cycles; + + vmexit_sum += cycles; + + test->vmcb->save.rip += 3; + + runs -= 1; + + return runs == 0; +} + +static bool latency_check(struct test *test) +{ + printf(" Latency VMRUN : max: %d min: %d avg: %d\n", latvmrun_max, + latvmrun_min, vmrun_sum / LATENCY_RUNS); + printf(" Latency VMEXIT: max: %d min: %d avg: %d\n", latvmexit_max, + latvmexit_min, vmexit_sum / LATENCY_RUNS); + return true; +} + +static void lat_svm_insn_prepare(struct test *test) +{ + default_prepare(test); + runs = LATENCY_RUNS; + latvmload_min = latvmsave_min = latstgi_min = latclgi_min = -1ULL; + latvmload_max = latvmsave_max = latstgi_max = latclgi_max = 0; + vmload_sum = vmsave_sum = stgi_sum = clgi_sum; +} + +static bool lat_svm_insn_finished(struct test *test) +{ + u64 vmcb_phys = virt_to_phys(test->vmcb); + u64 cycles; + + for ( ; runs != 0; runs--) { + tsc_start = rdtsc(); + asm volatile("vmload\n\t" : : "a"(vmcb_phys) : "memory"); + cycles = rdtsc() - tsc_start; + if (cycles > latvmload_max) + latvmload_max = cycles; + if (cycles < latvmload_min) + latvmload_min = cycles; + vmload_sum += cycles; + + tsc_start = rdtsc(); + asm volatile("vmsave\n\t" : : "a"(vmcb_phys) : "memory"); + cycles = rdtsc() - tsc_start; + if (cycles > latvmsave_max) + latvmsave_max = cycles; + if (cycles < latvmsave_min) + latvmsave_min = cycles; + vmsave_sum += cycles; + + tsc_start = rdtsc(); + asm volatile("stgi\n\t"); + cycles = rdtsc() - tsc_start; + if (cycles > latstgi_max) + latstgi_max = cycles; + if (cycles < latstgi_min) + latstgi_min = cycles; + stgi_sum += cycles; + + tsc_start = rdtsc(); + asm volatile("clgi\n\t"); + cycles = rdtsc() - tsc_start; + if (cycles > latclgi_max) + latclgi_max = cycles; + if (cycles < latclgi_min) + latclgi_min = cycles; + clgi_sum += cycles; + } + + return true; +} + +static bool lat_svm_insn_check(struct test *test) +{ + printf(" Latency VMLOAD: max: %d min: %d avg: %d\n", latvmload_max, + latvmload_min, vmload_sum / LATENCY_RUNS); + printf(" Latency VMSAVE: max: %d min: %d avg: %d\n", latvmsave_max, + latvmsave_min, vmsave_sum / LATENCY_RUNS); + printf(" Latency STGI: max: %d min: %d avg: %d\n", latstgi_max, + latstgi_min, stgi_sum / LATENCY_RUNS); + printf(" Latency CLGI: max: %d min: %d avg: %d\n", latclgi_max, + latclgi_min, clgi_sum / LATENCY_RUNS); + return true; +} +static struct test tests[] = { + { "null", default_supported, default_prepare, null_test, + default_finished, null_check }, + { "vmrun", default_supported, default_prepare, test_vmrun, + default_finished, check_vmrun }, + { "vmrun intercept check", default_supported, prepare_no_vmrun_int, + null_test, default_finished, check_no_vmrun_int }, + { "cr3 read intercept", default_supported, prepare_cr3_intercept, + test_cr3_intercept, default_finished, check_cr3_intercept }, + { "cr3 read nointercept", default_supported, default_prepare, + test_cr3_intercept, default_finished, check_cr3_nointercept }, + { "cr3 read intercept emulate", smp_supported, + prepare_cr3_intercept_bypass, test_cr3_intercept_bypass, + default_finished, check_cr3_intercept }, + { "next_rip", next_rip_supported, prepare_next_rip, test_next_rip, + default_finished, check_next_rip }, + { "mode_switch", default_supported, prepare_mode_switch, test_mode_switch, + mode_switch_finished, check_mode_switch }, + { "asid_zero", default_supported, prepare_asid_zero, test_asid_zero, + default_finished, check_asid_zero }, + { "sel_cr0_bug", default_supported, sel_cr0_bug_prepare, sel_cr0_bug_test, + sel_cr0_bug_finished, sel_cr0_bug_check }, + { "npt_nx", npt_supported, npt_nx_prepare, null_test, + default_finished, npt_nx_check }, + { "npt_us", npt_supported, npt_us_prepare, npt_us_test, + default_finished, npt_us_check }, + { "npt_rsvd", npt_supported, npt_rsvd_prepare, null_test, + default_finished, npt_rsvd_check }, + { "npt_rw", npt_supported, npt_rw_prepare, npt_rw_test, + default_finished, npt_rw_check }, + { "npt_pfwalk", npt_supported, npt_pfwalk_prepare, null_test, + default_finished, npt_pfwalk_check }, + { "latency_run_exit", default_supported, latency_prepare, latency_test, + latency_finished, latency_check }, + { "latency_svm_insn", default_supported, lat_svm_insn_prepare, null_test, + lat_svm_insn_finished, lat_svm_insn_check }, +}; + +int main(int ac, char **av) +{ + int i, nr, passed, done; + struct vmcb *vmcb; + + setup_vm(); + smp_init(); + + if (!(cpuid(0x80000001).c & 4)) { + printf("SVM not availble\n"); + return 0; + } + + setup_svm(); + + vmcb = alloc_page(); + + nr = ARRAY_SIZE(tests); + passed = done = 0; + for (i = 0; i < nr; ++i) { + if (!tests[i].supported()) + continue; + done += 1; + passed += test_run(&tests[i], vmcb); + } + + printf("\nSUMMARY: %d TESTS, %d FAILURES\n", done, (done - passed)); + return passed == done ? 0 : 1; +} diff --git a/kvm-unittest/x86/taskswitch.c b/kvm-unittest/x86/taskswitch.c new file mode 100644 index 0000000..8ed8a93 --- /dev/null +++ b/kvm-unittest/x86/taskswitch.c @@ -0,0 +1,164 @@ +/* + * Copyright 2010 Siemens AG + * Author: Jan Kiszka + * + * Released under GPLv2. + */ + +#include "libcflat.h" + +#define FIRST_SPARE_SEL 0x18 + +struct exception_frame { + unsigned long error_code; + unsigned long ip; + unsigned long cs; + unsigned long flags; +}; + +struct tss32 { + unsigned short prev; + unsigned short res1; + unsigned long esp0; + unsigned short ss0; + unsigned short res2; + unsigned long esp1; + unsigned short ss1; + unsigned short res3; + unsigned long esp2; + unsigned short ss2; + unsigned short res4; + unsigned long cr3; + unsigned long eip; + unsigned long eflags; + unsigned long eax, ecx, edx, ebx, esp, ebp, esi, edi; + unsigned short es; + unsigned short res5; + unsigned short cs; + unsigned short res6; + unsigned short ss; + unsigned short res7; + unsigned short ds; + unsigned short res8; + unsigned short fs; + unsigned short res9; + unsigned short gs; + unsigned short res10; + unsigned short ldt; + unsigned short res11; + unsigned short t:1; + unsigned short res12:15; + unsigned short iomap_base; +}; + +static char main_stack[4096]; +static char fault_stack[4096]; +static struct tss32 main_tss; +static struct tss32 fault_tss; + +static unsigned long long gdt[] __attribute__((aligned(16))) = { + 0, + 0x00cf9b000000ffffull, + 0x00cf93000000ffffull, + 0, 0, /* TSS segments */ + 0, /* task return gate */ +}; + +static unsigned long long gdtr; + +void fault_entry(void); + +static __attribute__((used, regparm(1))) void +fault_handler(unsigned long error_code) +{ + unsigned short *desc; + + printf("fault at %x:%x, prev task %x, error code %x\n", + main_tss.cs, main_tss.eip, fault_tss.prev, error_code); + + main_tss.eip += 2; + + desc = (unsigned short *)&gdt[3]; + desc[2] &= ~0x0200; + + desc = (unsigned short *)&gdt[5]; + desc[0] = 0; + desc[1] = fault_tss.prev; + desc[2] = 0x8500; + desc[3] = 0; +} + +asm ( + "fault_entry:\n" + " mov (%esp),%eax\n" + " call fault_handler\n" + " jmp $0x28, $0\n" +); + +static void setup_tss(struct tss32 *tss, void *entry, + void *stack_base, unsigned long stack_size) +{ + unsigned long cr3; + unsigned short cs, ds; + + asm ("mov %%cr3,%0" : "=r" (cr3)); + asm ("mov %%cs,%0" : "=r" (cs)); + asm ("mov %%ds,%0" : "=r" (ds)); + + tss->ss0 = tss->ss1 = tss->ss2 = tss->ss = ds; + tss->esp0 = tss->esp1 = tss->esp2 = tss->esp = + (unsigned long)stack_base + stack_size; + tss->ds = tss->es = tss->fs = tss->gs = ds; + tss->cs = cs; + tss->eip = (unsigned long)entry; + tss->cr3 = cr3; +} + +static void setup_tss_desc(unsigned short tss_sel, struct tss32 *tss) +{ + unsigned long addr = (unsigned long)tss; + unsigned short *desc; + + desc = (unsigned short *)&gdt[tss_sel/8]; + desc[0] = sizeof(*tss) - 1; + desc[1] = addr; + desc[2] = 0x8900 | ((addr & 0x00ff0000) >> 16); + desc[3] = (addr & 0xff000000) >> 16; +} + +static void set_intr_task(unsigned short tss_sel, int intr, struct tss32 *tss) +{ + unsigned short *desc = (void *)(intr* sizeof(long) * 2); + + setup_tss_desc(tss_sel, tss); + + desc[0] = 0; + desc[1] = tss_sel; + desc[2] = 0x8500; + desc[3] = 0; +} + +int main(int ac, char **av) +{ + const long invalid_segment = 0x1234; + + gdtr = ((unsigned long long)(unsigned long)&gdt << 16) | + (sizeof(gdt) - 1); + asm ("lgdt %0" : : "m" (gdtr)); + + setup_tss(&main_tss, 0, main_stack, sizeof(main_stack)); + setup_tss_desc(FIRST_SPARE_SEL, &main_tss); + asm ("ltr %0" : : "r" ((unsigned short)FIRST_SPARE_SEL)); + + setup_tss(&fault_tss, fault_entry, fault_stack, sizeof(fault_stack)); + set_intr_task(FIRST_SPARE_SEL+8, 13, &fault_tss); + + asm ( + "mov %0,%%es\n" + : : "r" (invalid_segment) : "edi" + ); + + printf("post fault\n"); + + return 0; +} diff --git a/kvm-unittest/x86/taskswitch2.c b/kvm-unittest/x86/taskswitch2.c new file mode 100644 index 0000000..6573696 --- /dev/null +++ b/kvm-unittest/x86/taskswitch2.c @@ -0,0 +1,279 @@ +#include "libcflat.h" +#include "desc.h" +#include "apic-defs.h" +#include "apic.h" +#include "processor.h" +#include "vm.h" + +#define FREE_GDT_INDEX 6 +#define MAIN_TSS_INDEX (FREE_GDT_INDEX + 0) +#define VM86_TSS_INDEX (FREE_GDT_INDEX + 1) + +#define xstr(s) str(s) +#define str(s) #s + +static volatile int test_count; +static volatile unsigned int test_divider; + +static char *fault_addr; +static ulong fault_phys; + +static int g_fail; +static int g_tests; + +static inline void io_delay(void) +{ +} + +static void report(const char *msg, int pass) +{ + ++g_tests; + printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL")); + if (!pass) + ++g_fail; +} + +static void nmi_tss(void) +{ +start: + printf("NMI task is running\n"); + print_current_tss_info(); + test_count++; + asm volatile ("iret"); + goto start; +} + +static void de_tss(void) +{ +start: + printf("DE task is running\n"); + print_current_tss_info(); + test_divider = 10; + test_count++; + asm volatile ("iret"); + goto start; +} + +static void of_tss(void) +{ +start: + printf("OF task is running\n"); + print_current_tss_info(); + test_count++; + asm volatile ("iret"); + goto start; +} + +static void bp_tss(void) +{ +start: + printf("BP task is running\n"); + print_current_tss_info(); + test_count++; + asm volatile ("iret"); + goto start; +} + +void do_pf_tss(ulong *error_code) +{ + printf("PF task is running %x %x\n", error_code, *(ulong*)error_code); + print_current_tss_info(); + if (*(ulong*)error_code == 0x2) /* write access, not present */ + test_count++; + install_pte(phys_to_virt(read_cr3()), 1, fault_addr, + fault_phys | PTE_PRESENT | PTE_WRITE, 0); +} + +extern void pf_tss(void); + +asm ( + "pf_tss: \n\t" + "push %esp \n\t" + "call do_pf_tss \n\t" + "add $4, %esp \n\t" + "iret\n\t" + "jmp pf_tss\n\t" + ); + +static void jmp_tss(void) +{ +start: + printf("JMP to task succeeded\n"); + print_current_tss_info(); + test_count++; + asm volatile ("ljmp $" xstr(TSS_MAIN) ", $0"); + goto start; +} + +static void irq_tss(void) +{ +start: + printf("IRQ task is running\n"); + print_current_tss_info(); + test_count++; + asm volatile ("iret"); + test_count++; + printf("IRQ task restarts after iret.\n"); + goto start; +} + +void test_kernel_mode_int() +{ + unsigned int res; + + /* test that int $2 triggers task gate */ + test_count = 0; + set_intr_task_gate(2, nmi_tss); + printf("Triggering nmi 2\n"); + asm volatile ("int $2"); + printf("Return from nmi %d\n", test_count); + report("NMI int $2", test_count == 1); + + /* test that external NMI triggers task gate */ + test_count = 0; + set_intr_task_gate(2, nmi_tss); + printf("Triggering nmi through APIC\n"); + apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0); + io_delay(); + printf("Return from APIC nmi\n"); + report("NMI external", test_count == 1); + + /* test that external interrupt triggesr task gate */ + test_count = 0; + printf("Trigger IRQ from APIC\n"); + set_intr_task_gate(0xf0, irq_tss); + irq_enable(); + apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | APIC_INT_ASSERT | 0xf0, 0); + io_delay(); + irq_disable(); + printf("Return from APIC IRQ\n"); + report("IRQ external", test_count == 1); + + /* test that HW exception triggesr task gate */ + set_intr_task_gate(0, de_tss); + printf("Try to devide by 0\n"); + asm volatile ("divl %3": "=a"(res) + : "d"(0), "a"(1500), "m"(test_divider)); + printf("Result is %d\n", res); + report("DE exeption", res == 150); + + /* test if call HW exeption DE by int $0 triggers task gate */ + test_count = 0; + set_intr_task_gate(0, de_tss); + printf("Call int 0\n"); + asm volatile ("int $0"); + printf("Return from int 0\n"); + report("int $0", test_count == 1); + + /* test if HW exception OF triggers task gate */ + test_count = 0; + set_intr_task_gate(4, of_tss); + printf("Call into\n"); + asm volatile ("addb $127, %b0\ninto"::"a"(127)); + printf("Return from into\n"); + report("OF exeption", test_count); + + /* test if HW exception BP triggers task gate */ + test_count = 0; + set_intr_task_gate(3, bp_tss); + printf("Call int 3\n"); + asm volatile ("int $3"); + printf("Return from int 3\n"); + report("BP exeption", test_count == 1); + + /* + * test that PF triggers task gate and error code is placed on + * exception task's stack + */ + fault_addr = alloc_vpage(); + fault_phys = (ulong)virt_to_phys(alloc_page()); + test_count = 0; + set_intr_task_gate(14, pf_tss); + printf("Access unmapped page\n"); + *fault_addr = 0; + printf("Return from pf tss\n"); + report("PF exeption", test_count == 1); + + /* test that calling a task by lcall works */ + test_count = 0; + set_intr_task_gate(0, irq_tss); + printf("Calling task by lcall\n"); + /* hlt opcode is 0xf4 I use destination IP 0xf4f4f4f4 to catch + incorrect instruction length calculation */ + asm volatile("lcall $" xstr(TSS_INTR) ", $0xf4f4f4f4"); + printf("Return from call\n"); + report("lcall", test_count == 1); + + /* call the same task again and check that it restarted after iret */ + test_count = 0; + asm volatile("lcall $" xstr(TSS_INTR) ", $0xf4f4f4f4"); + report("lcall2", test_count == 2); + + /* test that calling a task by ljmp works */ + test_count = 0; + set_intr_task_gate(0, jmp_tss); + printf("Jumping to a task by ljmp\n"); + asm volatile ("ljmp $" xstr(TSS_INTR) ", $0xf4f4f4f4"); + printf("Jump back succeeded\n"); + report("ljmp", test_count == 1); +} + +void test_vm86_switch(void) +{ + static tss32_t main_tss; + static tss32_t vm86_tss; + + u8 *vm86_start; + + /* Write a 'ud2' instruction somewhere below 1 MB */ + vm86_start = (void*) 0x42000; + vm86_start[0] = 0x0f; + vm86_start[1] = 0x0b; + + /* Main TSS */ + set_gdt_entry(MAIN_TSS_INDEX, (u32)&main_tss, sizeof(tss32_t) - 1, 0x89, 0); + ltr(MAIN_TSS_INDEX << 3); + main_tss = (tss32_t) { + .prev = VM86_TSS_INDEX << 3, + .cr3 = read_cr3(), + }; + + /* VM86 TSS (marked as busy, so we can iret to it) */ + set_gdt_entry(VM86_TSS_INDEX, (u32)&vm86_tss, sizeof(tss32_t) - 1, 0x8b, 0); + vm86_tss = (tss32_t) { + .eflags = 0x20002, + .cr3 = read_cr3(), + .eip = (u32) vm86_start & 0x0f, + .cs = (u32) vm86_start >> 4, + .ds = 0x1234, + .es = 0x2345, + }; + + /* Setup task gate to main TSS for #UD */ + set_idt_task_gate(6, MAIN_TSS_INDEX << 3); + + /* Jump into VM86 task with iret, #UD lets it come back immediately */ + printf("Switch to VM86 task and back\n"); + asm volatile( + "pushf\n" + "orw $0x4000, (%esp)\n" + "popf\n" + "iret\n" + ); + report("VM86", 1); +} + +int main() +{ + setup_vm(); + setup_idt(); + setup_gdt(); + setup_tss32(); + + test_kernel_mode_int(); + test_vm86_switch(); + + printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail); + + return g_fail != 0; +} diff --git a/kvm-unittest/x86/tsc.c b/kvm-unittest/x86/tsc.c new file mode 100644 index 0000000..58f332d --- /dev/null +++ b/kvm-unittest/x86/tsc.c @@ -0,0 +1,24 @@ +#include "libcflat.h" +#include "processor.h" + +void test_wrtsc(u64 t1) +{ + u64 t2; + + wrtsc(t1); + t2 = rdtsc(); + printf("rdtsc after wrtsc(%lld): %lld\n", t1, t2); +} + +int main() +{ + u64 t1, t2; + + t1 = rdtsc(); + t2 = rdtsc(); + printf("rdtsc latency %lld\n", (unsigned)(t2 - t1)); + + test_wrtsc(0); + test_wrtsc(100000000000ull); + return 0; +} diff --git a/kvm-unittest/x86/tsc_adjust.c b/kvm-unittest/x86/tsc_adjust.c new file mode 100644 index 0000000..0e96792 --- /dev/null +++ b/kvm-unittest/x86/tsc_adjust.c @@ -0,0 +1,60 @@ +#include "libcflat.h" +#include "processor.h" + +#define IA32_TSC_ADJUST 0x3b + +int main() +{ + u64 t1, t2, t3, t4, t5; + u64 est_delta_time; + bool pass = true; + + if (cpuid(7).b & (1 << 1)) { // IA32_TSC_ADJUST Feature is enabled? + if ( rdmsr(IA32_TSC_ADJUST) != 0x0) { + printf("failure: IA32_TSC_ADJUST msr was incorrectly" + " initialized\n"); + pass = false; + } + t3 = 100000000000ull; + t1 = rdtsc(); + wrmsr(IA32_TSC_ADJUST, t3); + t2 = rdtsc(); + if (rdmsr(IA32_TSC_ADJUST) != t3) { + printf("failure: IA32_TSC_ADJUST msr read / write" + " incorrect\n"); + pass = false; + } + if (t2 - t1 < t3) { + printf("failure: TSC did not adjust for IA32_TSC_ADJUST" + " value\n"); + pass = false; + } + t3 = 0x0; + wrmsr(IA32_TSC_ADJUST, t3); + if (rdmsr(IA32_TSC_ADJUST) != t3) { + printf("failure: IA32_TSC_ADJUST msr read / write" + " incorrect\n"); + pass = false; + } + t4 = 100000000000ull; + t1 = rdtsc(); + wrtsc(t4); + t2 = rdtsc(); + t5 = rdmsr(IA32_TSC_ADJUST); + // est of time between reading tsc and writing tsc, + // (based on IA32_TSC_ADJUST msr value) should be small + est_delta_time = t4 - t5 - t1; + if (est_delta_time > 2 * (t2 - t4)) { + // arbitray 2x latency (wrtsc->rdtsc) threshold + printf("failure: IA32_TSC_ADJUST msr incorrectly" + " adjusted on tsc write\n"); + pass = false; + } + if (pass) printf("success: IA32_TSC_ADJUST enabled and" + " working correctly\n"); + } + else { + printf("success: IA32_TSC_ADJUST feature not enabled\n"); + } + return pass?0:1; +} diff --git a/kvm-unittest/x86/vmexit.c b/kvm-unittest/x86/vmexit.c new file mode 100644 index 0000000..3b945de --- /dev/null +++ b/kvm-unittest/x86/vmexit.c @@ -0,0 +1,448 @@ +#include "libcflat.h" +#include "smp.h" +#include "processor.h" +#include "atomic.h" +#include "x86/vm.h" +#include "x86/desc.h" +#include "x86/pci.h" + +struct test { + void (*func)(void); + const char *name; + int (*valid)(void); + int parallel; + bool (*next)(struct test *); +}; + +static void outb(unsigned short port, unsigned val) +{ + asm volatile("outb %b0, %w1" : : "a"(val), "Nd"(port)); +} + +static void outw(unsigned short port, unsigned val) +{ + asm volatile("outw %w0, %w1" : : "a"(val), "Nd"(port)); +} + +static void outl(unsigned short port, unsigned val) +{ + asm volatile("outl %d0, %w1" : : "a"(val), "Nd"(port)); +} + +static unsigned int inb(unsigned short port) +{ + unsigned int val; + asm volatile("xorl %0, %0; inb %w1, %b0" : "=a"(val) : "Nd"(port)); + return val; +} + +static unsigned int inl(unsigned short port) +{ + unsigned int val; + asm volatile("inl %w1, %0" : "=a"(val) : "Nd"(port)); + return val; +} + +#define GOAL (1ull << 30) + +static int nr_cpus; + +#ifdef __x86_64__ +# define R "r" +#else +# define R "e" +#endif + +static void cpuid_test(void) +{ + asm volatile ("push %%"R "bx; cpuid; pop %%"R "bx" + : : : "eax", "ecx", "edx"); +} + +static void vmcall(void) +{ + unsigned long a = 0, b, c, d; + + asm volatile ("vmcall" : "+a"(a), "=b"(b), "=c"(c), "=d"(d)); +} + +#define MSR_TSC_ADJUST 0x3b +#define MSR_EFER 0xc0000080 +#define EFER_NX_MASK (1ull << 11) + +#ifdef __x86_64__ +static void mov_from_cr8(void) +{ + unsigned long cr8; + + asm volatile ("mov %%cr8, %0" : "=r"(cr8)); +} + +static void mov_to_cr8(void) +{ + unsigned long cr8 = 0; + + asm volatile ("mov %0, %%cr8" : : "r"(cr8)); +} +#endif + +static int is_smp(void) +{ + return cpu_count() > 1; +} + +static void nop(void *junk) +{ +} + +static void ipi(void) +{ + on_cpu(1, nop, 0); +} + +static void ipi_halt(void) +{ + unsigned long long t; + + on_cpu(1, nop, 0); + t = rdtsc() + 2000; + while (rdtsc() < t) + ; +} + +static void inl_pmtimer(void) +{ + inl(0xb008); +} + +static void inl_nop_qemu(void) +{ + inl(0x1234); +} + +static void inl_nop_kernel(void) +{ + inb(0x4d0); +} + +static void outl_elcr_kernel(void) +{ + outb(0x4d0, 0); +} + +static void ple_round_robin(void) +{ + struct counter { + volatile int n1; + int n2; + } __attribute__((aligned(64))); + static struct counter counters[64] = { { -1, 0 } }; + int me = smp_id(); + int you; + volatile struct counter *p = &counters[me]; + + while (p->n1 == p->n2) + asm volatile ("pause"); + + p->n2 = p->n1; + you = me + 1; + if (you == nr_cpus) + you = 0; + ++counters[you].n1; +} + +static void rd_tsc_adjust_msr(void) +{ + rdmsr(MSR_TSC_ADJUST); +} + +static void wr_tsc_adjust_msr(void) +{ + wrmsr(MSR_TSC_ADJUST, 0x0); +} + +struct pci_test_dev_hdr { + uint8_t test; + uint8_t width; + uint8_t pad0[2]; + uint32_t offset; + uint32_t data; + uint32_t count; + uint8_t name[]; +}; + +static struct pci_test { + unsigned iobar; + unsigned ioport; + volatile void *memaddr; + volatile void *mem; + int test_idx; + uint32_t data; + uint32_t offset; +} pci_test = { + .test_idx = -1 +}; + +static void pci_mem_testb(void) +{ + *(volatile uint8_t *)pci_test.mem = pci_test.data; +} + +static void pci_mem_testw(void) +{ + *(volatile uint16_t *)pci_test.mem = pci_test.data; +} + +static void pci_mem_testl(void) +{ + *(volatile uint32_t *)pci_test.mem = pci_test.data; +} + +static void pci_io_testb(void) +{ + outb(pci_test.ioport, pci_test.data); +} + +static void pci_io_testw(void) +{ + outw(pci_test.ioport, pci_test.data); +} + +static void pci_io_testl(void) +{ + outl(pci_test.ioport, pci_test.data); +} + +static uint8_t ioreadb(unsigned long addr, bool io) +{ + if (io) { + return inb(addr); + } else { + return *(volatile uint8_t *)addr; + } +} + +static uint32_t ioreadl(unsigned long addr, bool io) +{ + /* Note: assumes little endian */ + if (io) { + return inl(addr); + } else { + return *(volatile uint32_t *)addr; + } +} + +static void iowriteb(unsigned long addr, uint8_t data, bool io) +{ + if (io) { + outb(addr, data); + } else { + *(volatile uint8_t *)addr = data; + } +} + +static bool pci_next(struct test *test, unsigned long addr, bool io) +{ + int i; + uint8_t width; + + if (!pci_test.memaddr) { + test->func = NULL; + return true; + } + pci_test.test_idx++; + iowriteb(addr + offsetof(struct pci_test_dev_hdr, test), + pci_test.test_idx, io); + width = ioreadb(addr + offsetof(struct pci_test_dev_hdr, width), + io); + switch (width) { + case 1: + test->func = io ? pci_io_testb : pci_mem_testb; + break; + case 2: + test->func = io ? pci_io_testw : pci_mem_testw; + break; + case 4: + test->func = io ? pci_io_testl : pci_mem_testl; + break; + default: + /* Reset index for purposes of the next test */ + pci_test.test_idx = -1; + test->func = NULL; + return false; + } + pci_test.data = ioreadl(addr + offsetof(struct pci_test_dev_hdr, data), + io); + pci_test.offset = ioreadl(addr + offsetof(struct pci_test_dev_hdr, + offset), io); + for (i = 0; i < pci_test.offset; ++i) { + char c = ioreadb(addr + offsetof(struct pci_test_dev_hdr, + name) + i, io); + if (!c) { + break; + } + printf("%c",c); + } + printf(":"); + return true; +} + +static bool pci_mem_next(struct test *test) +{ + bool ret; + ret = pci_next(test, ((unsigned long)pci_test.memaddr), false); + if (ret) { + pci_test.mem = pci_test.memaddr + pci_test.offset; + } + return ret; +} + +static bool pci_io_next(struct test *test) +{ + bool ret; + ret = pci_next(test, ((unsigned long)pci_test.iobar), true); + if (ret) { + pci_test.ioport = pci_test.iobar + pci_test.offset; + } + return ret; +} + +static struct test tests[] = { + { cpuid_test, "cpuid", .parallel = 1, }, + { vmcall, "vmcall", .parallel = 1, }, +#ifdef __x86_64__ + { mov_from_cr8, "mov_from_cr8", .parallel = 1, }, + { mov_to_cr8, "mov_to_cr8" , .parallel = 1, }, +#endif + { inl_pmtimer, "inl_from_pmtimer", .parallel = 1, }, + { inl_nop_qemu, "inl_from_qemu", .parallel = 1 }, + { inl_nop_kernel, "inl_from_kernel", .parallel = 1 }, + { outl_elcr_kernel, "outl_to_kernel", .parallel = 1 }, + { ipi, "ipi", is_smp, .parallel = 0, }, + { ipi_halt, "ipi+halt", is_smp, .parallel = 0, }, + { ple_round_robin, "ple-round-robin", .parallel = 1 }, + { wr_tsc_adjust_msr, "wr_tsc_adjust_msr", .parallel = 1 }, + { rd_tsc_adjust_msr, "rd_tsc_adjust_msr", .parallel = 1 }, + { NULL, "pci-mem", .parallel = 0, .next = pci_mem_next }, + { NULL, "pci-io", .parallel = 0, .next = pci_io_next }, +}; + +unsigned iterations; +static atomic_t nr_cpus_done; + +static void run_test(void *_func) +{ + int i; + void (*func)(void) = _func; + + for (i = 0; i < iterations; ++i) + func(); + + atomic_inc(&nr_cpus_done); +} + +static bool do_test(struct test *test) +{ + int i; + unsigned long long t1, t2; + void (*func)(void); + + iterations = 32; + + if (test->valid && !test->valid()) { + printf("%s (skipped)\n", test->name); + return false; + } + + if (test->next && !test->next(test)) { + return false; + } + + func = test->func; + if (!func) { + printf("%s (skipped)\n", test->name); + return false; + } + + do { + iterations *= 2; + t1 = rdtsc(); + + if (!test->parallel) { + 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()) + ; + } + t2 = rdtsc(); + } while ((t2 - t1) < GOAL); + printf("%s %d\n", test->name, (int)((t2 - t1) / iterations)); + return test->next; +} + +static void enable_nx(void *junk) +{ + if (cpuid(0x80000001).d & (1 << 20)) + wrmsr(MSR_EFER, rdmsr(MSR_EFER) | EFER_NX_MASK); +} + +bool test_wanted(struct test *test, char *wanted[], int nwanted) +{ + int i; + + if (!nwanted) + return true; + + for (i = 0; i < nwanted; ++i) + if (strcmp(wanted[i], test->name) == 0) + return true; + + return false; +} + +int main(int ac, char **av) +{ + int i; + unsigned long membar = 0, base, offset; + void *m; + pcidevaddr_t pcidev; + + smp_init(); + setup_vm(); + nr_cpus = cpu_count(); + + for (i = cpu_count(); i > 0; i--) + on_cpu(i-1, enable_nx, 0); + + pcidev = pci_find_dev(0x1b36, 0x0005); + if (pcidev) { + for (i = 0; i < 2; i++) { + if (!pci_bar_is_valid(pcidev, i)) { + continue; + } + if (pci_bar_is_memory(pcidev, i)) { + membar = pci_bar_addr(pcidev, i); + base = membar & ~4095; + offset = membar - base; + m = alloc_vpages(1); + + install_page((void *)read_cr3(), base, m); + pci_test.memaddr = m + offset; + } else { + pci_test.iobar = pci_bar_addr(pcidev, i); + } + } + printf("pci-testdev at 0x%x membar %lx iobar %x\n", + pcidev, membar, pci_test.iobar); + } + + for (i = 0; i < ARRAY_SIZE(tests); ++i) + if (test_wanted(&tests[i], av + 1, ac - 1)) + while (do_test(&tests[i])) {} + + return 0; +} diff --git a/kvm-unittest/x86/vmx.c b/kvm-unittest/x86/vmx.c new file mode 100644 index 0000000..ca36d35 --- /dev/null +++ b/kvm-unittest/x86/vmx.c @@ -0,0 +1,600 @@ +#include "libcflat.h" +#include "processor.h" +#include "vm.h" +#include "desc.h" +#include "vmx.h" +#include "msr.h" +#include "smp.h" +#include "io.h" + +int fails, tests; +u32 *vmxon_region; +struct vmcs *vmcs_root; +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; +u64 hypercall_field; +bool launched; +u64 host_rflags; + +union vmx_basic basic; +union vmx_ctrl_pin ctrl_pin_rev; +union vmx_ctrl_cpu ctrl_cpu_rev[2]; +union vmx_ctrl_exit ctrl_exit_rev; +union vmx_ctrl_ent ctrl_enter_rev; +union vmx_ept_vpid ept_vpid; + +extern u64 gdt64_desc[]; +extern u64 idt_descr[]; +extern u64 tss_descr[]; +extern void *vmx_return; +extern void *entry_sysenter; +extern void *guest_entry; + +void report(const char *name, int result) +{ + ++tests; + if (result) + printf("PASS: %s\n", name); + else { + printf("FAIL: %s\n", name); + ++fails; + } +} + +static int make_vmcs_current(struct vmcs *vmcs) +{ + bool ret; + + asm volatile ("vmptrld %1; setbe %0" : "=q" (ret) : "m" (vmcs) : "cc"); + return ret; +} + +/* entry_sysenter */ +asm( + ".align 4, 0x90\n\t" + ".globl entry_sysenter\n\t" + "entry_sysenter:\n\t" + SAVE_GPR + " and $0xf, %rax\n\t" + " mov %rax, %rdi\n\t" + " call syscall_handler\n\t" + LOAD_GPR + " vmresume\n\t" +); + +static void __attribute__((__used__)) syscall_handler(u64 syscall_no) +{ + current->syscall_handler(syscall_no); +} + +static inline int vmx_on() +{ + bool ret; + asm volatile ("vmxon %1; setbe %0\n\t" + : "=q"(ret) : "m"(vmxon_region) : "cc"); + return ret; +} + +static inline int vmx_off() +{ + bool ret; + asm volatile("vmxoff; setbe %0\n\t" + : "=q"(ret) : : "cc"); + return ret; +} + +void print_vmexit_info() +{ + u64 guest_rip, guest_rsp; + ulong reason = vmcs_read(EXI_REASON) & 0xff; + ulong exit_qual = vmcs_read(EXI_QUALIFICATION); + guest_rip = vmcs_read(GUEST_RIP); + guest_rsp = vmcs_read(GUEST_RSP); + printf("VMEXIT info:\n"); + printf("\tvmexit reason = %d\n", reason); + printf("\texit qualification = 0x%x\n", exit_qual); + printf("\tBit 31 of reason = %x\n", (vmcs_read(EXI_REASON) >> 31) & 1); + printf("\tguest_rip = 0x%llx\n", guest_rip); + printf("\tRAX=0x%llx RBX=0x%llx RCX=0x%llx RDX=0x%llx\n", + regs.rax, regs.rbx, regs.rcx, regs.rdx); + printf("\tRSP=0x%llx RBP=0x%llx RSI=0x%llx RDI=0x%llx\n", + guest_rsp, regs.rbp, regs.rsi, regs.rdi); + printf("\tR8 =0x%llx R9 =0x%llx R10=0x%llx R11=0x%llx\n", + regs.r8, regs.r9, regs.r10, regs.r11); + printf("\tR12=0x%llx R13=0x%llx R14=0x%llx R15=0x%llx\n", + regs.r12, regs.r13, regs.r14, regs.r15); +} + +static void test_vmclear(void) +{ + u64 rflags; + + rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + write_rflags(rflags); + report("test vmclear", vmcs_clear(vmcs_root) == 0); +} + +static void test_vmxoff(void) +{ + int ret; + u64 rflags; + + rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + write_rflags(rflags); + ret = vmx_off(); + report("test vmxoff", !ret); +} + +static void __attribute__((__used__)) guest_main(void) +{ + current->guest_main(); +} + +/* guest_entry */ +asm( + ".align 4, 0x90\n\t" + ".globl entry_guest\n\t" + "guest_entry:\n\t" + " call guest_main\n\t" + " mov $1, %edi\n\t" + " call hypercall\n\t" +); + +static void init_vmcs_ctrl(void) +{ + /* 26.2 CHECKS ON VMX CONTROLS AND HOST-STATE AREA */ + /* 26.2.1.1 */ + vmcs_write(PIN_CONTROLS, ctrl_pin); + /* Disable VMEXIT of IO instruction */ + vmcs_write(CPU_EXEC_CTRL0, ctrl_cpu[0]); + if (ctrl_cpu_rev[0].set & CPU_SECONDARY) { + ctrl_cpu[1] |= ctrl_cpu_rev[1].set & ctrl_cpu_rev[1].clr; + vmcs_write(CPU_EXEC_CTRL1, ctrl_cpu[1]); + } + vmcs_write(CR3_TARGET_COUNT, 0); + vmcs_write(VPID, ++vpid_cnt); +} + +static void init_vmcs_host(void) +{ + /* 26.2 CHECKS ON VMX CONTROLS AND HOST-STATE AREA */ + /* 26.2.1.2 */ + vmcs_write(HOST_EFER, rdmsr(MSR_EFER)); + + /* 26.2.1.3 */ + vmcs_write(ENT_CONTROLS, ctrl_enter); + vmcs_write(EXI_CONTROLS, ctrl_exit); + + /* 26.2.2 */ + vmcs_write(HOST_CR0, read_cr0()); + vmcs_write(HOST_CR3, read_cr3()); + vmcs_write(HOST_CR4, read_cr4()); + vmcs_write(HOST_SYSENTER_EIP, (u64)(&entry_sysenter)); + vmcs_write(HOST_SYSENTER_CS, SEL_KERN_CODE_64); + + /* 26.2.3 */ + vmcs_write(HOST_SEL_CS, SEL_KERN_CODE_64); + vmcs_write(HOST_SEL_SS, SEL_KERN_DATA_64); + vmcs_write(HOST_SEL_DS, SEL_KERN_DATA_64); + vmcs_write(HOST_SEL_ES, SEL_KERN_DATA_64); + vmcs_write(HOST_SEL_FS, SEL_KERN_DATA_64); + vmcs_write(HOST_SEL_GS, SEL_KERN_DATA_64); + vmcs_write(HOST_SEL_TR, SEL_TSS_RUN); + vmcs_write(HOST_BASE_TR, (u64)tss_descr); + vmcs_write(HOST_BASE_GDTR, (u64)gdt64_desc); + vmcs_write(HOST_BASE_IDTR, (u64)idt_descr); + vmcs_write(HOST_BASE_FS, 0); + vmcs_write(HOST_BASE_GS, 0); + + /* Set other vmcs area */ + vmcs_write(PF_ERROR_MASK, 0); + vmcs_write(PF_ERROR_MATCH, 0); + vmcs_write(VMCS_LINK_PTR, ~0ul); + vmcs_write(VMCS_LINK_PTR_HI, ~0ul); + vmcs_write(HOST_RIP, (u64)(&vmx_return)); +} + +static void init_vmcs_guest(void) +{ + /* 26.3 CHECKING AND LOADING GUEST STATE */ + ulong guest_cr0, guest_cr4, guest_cr3; + /* 26.3.1.1 */ + guest_cr0 = read_cr0(); + guest_cr4 = read_cr4(); + guest_cr3 = read_cr3(); + if (ctrl_enter & ENT_GUEST_64) { + guest_cr0 |= X86_CR0_PG; + guest_cr4 |= X86_CR4_PAE; + } + if ((ctrl_enter & ENT_GUEST_64) == 0) + guest_cr4 &= (~X86_CR4_PCIDE); + if (guest_cr0 & X86_CR0_PG) + guest_cr0 |= X86_CR0_PE; + vmcs_write(GUEST_CR0, guest_cr0); + vmcs_write(GUEST_CR3, guest_cr3); + vmcs_write(GUEST_CR4, guest_cr4); + vmcs_write(GUEST_SYSENTER_CS, SEL_KERN_CODE_64); + vmcs_write(GUEST_SYSENTER_ESP, + (u64)(guest_syscall_stack + PAGE_SIZE - 1)); + vmcs_write(GUEST_SYSENTER_EIP, (u64)(&entry_sysenter)); + vmcs_write(GUEST_DR7, 0); + vmcs_write(GUEST_EFER, rdmsr(MSR_EFER)); + + /* 26.3.1.2 */ + vmcs_write(GUEST_SEL_CS, SEL_KERN_CODE_64); + vmcs_write(GUEST_SEL_SS, SEL_KERN_DATA_64); + vmcs_write(GUEST_SEL_DS, SEL_KERN_DATA_64); + vmcs_write(GUEST_SEL_ES, SEL_KERN_DATA_64); + vmcs_write(GUEST_SEL_FS, SEL_KERN_DATA_64); + vmcs_write(GUEST_SEL_GS, SEL_KERN_DATA_64); + vmcs_write(GUEST_SEL_TR, SEL_TSS_RUN); + vmcs_write(GUEST_SEL_LDTR, 0); + + vmcs_write(GUEST_BASE_CS, 0); + vmcs_write(GUEST_BASE_ES, 0); + vmcs_write(GUEST_BASE_SS, 0); + vmcs_write(GUEST_BASE_DS, 0); + vmcs_write(GUEST_BASE_FS, 0); + vmcs_write(GUEST_BASE_GS, 0); + vmcs_write(GUEST_BASE_TR, (u64)tss_descr); + vmcs_write(GUEST_BASE_LDTR, 0); + + vmcs_write(GUEST_LIMIT_CS, 0xFFFFFFFF); + vmcs_write(GUEST_LIMIT_DS, 0xFFFFFFFF); + vmcs_write(GUEST_LIMIT_ES, 0xFFFFFFFF); + vmcs_write(GUEST_LIMIT_SS, 0xFFFFFFFF); + vmcs_write(GUEST_LIMIT_FS, 0xFFFFFFFF); + vmcs_write(GUEST_LIMIT_GS, 0xFFFFFFFF); + vmcs_write(GUEST_LIMIT_LDTR, 0xffff); + vmcs_write(GUEST_LIMIT_TR, ((struct descr *)tss_descr)->limit); + + vmcs_write(GUEST_AR_CS, 0xa09b); + vmcs_write(GUEST_AR_DS, 0xc093); + vmcs_write(GUEST_AR_ES, 0xc093); + vmcs_write(GUEST_AR_FS, 0xc093); + vmcs_write(GUEST_AR_GS, 0xc093); + vmcs_write(GUEST_AR_SS, 0xc093); + vmcs_write(GUEST_AR_LDTR, 0x82); + vmcs_write(GUEST_AR_TR, 0x8b); + + /* 26.3.1.3 */ + vmcs_write(GUEST_BASE_GDTR, (u64)gdt64_desc); + vmcs_write(GUEST_BASE_IDTR, (u64)idt_descr); + vmcs_write(GUEST_LIMIT_GDTR, + ((struct descr *)gdt64_desc)->limit & 0xffff); + vmcs_write(GUEST_LIMIT_IDTR, + ((struct descr *)idt_descr)->limit & 0xffff); + + /* 26.3.1.4 */ + vmcs_write(GUEST_RIP, (u64)(&guest_entry)); + vmcs_write(GUEST_RSP, (u64)(guest_stack + PAGE_SIZE - 1)); + vmcs_write(GUEST_RFLAGS, 0x2); + + /* 26.3.1.5 */ + vmcs_write(GUEST_ACTV_STATE, 0); + vmcs_write(GUEST_INTR_STATE, 0); +} + +static int init_vmcs(struct vmcs **vmcs) +{ + *vmcs = alloc_page(); + memset(*vmcs, 0, PAGE_SIZE); + (*vmcs)->revision_id = basic.revision; + /* vmclear first to init vmcs */ + if (vmcs_clear(*vmcs)) { + printf("%s : vmcs_clear error\n", __func__); + return 1; + } + + if (make_vmcs_current(*vmcs)) { + printf("%s : make_vmcs_current error\n", __func__); + return 1; + } + + /* All settings to pin/exit/enter/cpu + control fields should be placed here */ + ctrl_pin |= PIN_EXTINT | PIN_NMI | PIN_VIRT_NMI; + ctrl_exit = EXI_LOAD_EFER | EXI_HOST_64; + ctrl_enter = (ENT_LOAD_EFER | ENT_GUEST_64); + ctrl_cpu[0] |= CPU_HLT; + /* DIsable IO instruction VMEXIT now */ + ctrl_cpu[0] &= (~(CPU_IO | CPU_IO_BITMAP)); + ctrl_cpu[1] = 0; + + ctrl_pin = (ctrl_pin | ctrl_pin_rev.set) & ctrl_pin_rev.clr; + ctrl_enter = (ctrl_enter | ctrl_enter_rev.set) & ctrl_enter_rev.clr; + ctrl_exit = (ctrl_exit | ctrl_exit_rev.set) & ctrl_exit_rev.clr; + ctrl_cpu[0] = (ctrl_cpu[0] | ctrl_cpu_rev[0].set) & ctrl_cpu_rev[0].clr; + + init_vmcs_ctrl(); + init_vmcs_host(); + init_vmcs_guest(); + return 0; +} + +static void init_vmx(void) +{ + ulong fix_cr0_set, fix_cr0_clr; + ulong fix_cr4_set, fix_cr4_clr; + + vmxon_region = alloc_page(); + memset(vmxon_region, 0, PAGE_SIZE); + + fix_cr0_set = rdmsr(MSR_IA32_VMX_CR0_FIXED0); + fix_cr0_clr = rdmsr(MSR_IA32_VMX_CR0_FIXED1); + fix_cr4_set = rdmsr(MSR_IA32_VMX_CR4_FIXED0); + fix_cr4_clr = rdmsr(MSR_IA32_VMX_CR4_FIXED1); + basic.val = rdmsr(MSR_IA32_VMX_BASIC); + ctrl_pin_rev.val = rdmsr(basic.ctrl ? MSR_IA32_VMX_TRUE_PIN + : MSR_IA32_VMX_PINBASED_CTLS); + ctrl_exit_rev.val = rdmsr(basic.ctrl ? MSR_IA32_VMX_TRUE_EXIT + : MSR_IA32_VMX_EXIT_CTLS); + ctrl_enter_rev.val = rdmsr(basic.ctrl ? MSR_IA32_VMX_TRUE_ENTRY + : MSR_IA32_VMX_ENTRY_CTLS); + ctrl_cpu_rev[0].val = rdmsr(basic.ctrl ? MSR_IA32_VMX_TRUE_PROC + : MSR_IA32_VMX_PROCBASED_CTLS); + if (ctrl_cpu_rev[0].set & CPU_SECONDARY) + ctrl_cpu_rev[1].val = rdmsr(MSR_IA32_VMX_PROCBASED_CTLS2); + if (ctrl_cpu_rev[1].set & CPU_EPT || ctrl_cpu_rev[1].set & CPU_VPID) + ept_vpid.val = rdmsr(MSR_IA32_VMX_EPT_VPID_CAP); + + write_cr0((read_cr0() & fix_cr0_clr) | fix_cr0_set); + write_cr4((read_cr4() & fix_cr4_clr) | fix_cr4_set | X86_CR4_VMXE); + + *vmxon_region = basic.revision; + + guest_stack = alloc_page(); + memset(guest_stack, 0, PAGE_SIZE); + guest_syscall_stack = alloc_page(); + memset(guest_syscall_stack, 0, PAGE_SIZE); +} + +static int test_vmx_capability(void) +{ + struct cpuid r; + u64 ret1, ret2; + u64 ia32_feature_control; + r = cpuid(1); + ret1 = ((r.c) >> 5) & 1; + ia32_feature_control = rdmsr(MSR_IA32_FEATURE_CONTROL); + ret2 = ((ia32_feature_control & 0x5) == 0x5); + if ((!ret2) && ((ia32_feature_control & 0x1) == 0)) { + wrmsr(MSR_IA32_FEATURE_CONTROL, 0x5); + ia32_feature_control = rdmsr(MSR_IA32_FEATURE_CONTROL); + ret2 = ((ia32_feature_control & 0x5) == 0x5); + } + report("test vmx capability", ret1 & ret2); + return !(ret1 & ret2); +} + +static int test_vmxon(void) +{ + int ret; + u64 rflags; + + rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + write_rflags(rflags); + ret = vmx_on(); + report("test vmxon", !ret); + return ret; +} + +static void test_vmptrld(void) +{ + u64 rflags; + struct vmcs *vmcs; + + vmcs = alloc_page(); + vmcs->revision_id = basic.revision; + rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + write_rflags(rflags); + report("test vmptrld", make_vmcs_current(vmcs) == 0); +} + +static void test_vmptrst(void) +{ + u64 rflags; + int ret; + struct vmcs *vmcs1, *vmcs2; + + vmcs1 = alloc_page(); + memset(vmcs1, 0, PAGE_SIZE); + init_vmcs(&vmcs1); + rflags = read_rflags() | X86_EFLAGS_CF | X86_EFLAGS_ZF; + write_rflags(rflags); + ret = vmcs_save(&vmcs2); + report("test vmptrst", (!ret) && (vmcs1 == vmcs2)); +} + +/* This function can only be called in guest */ +static void __attribute__((__used__)) hypercall(u32 hypercall_no) +{ + u64 val = 0; + val = (hypercall_no & HYPERCALL_MASK) | HYPERCALL_BIT; + hypercall_field = val; + asm volatile("vmcall\n\t"); +} + +static bool is_hypercall() +{ + ulong reason, hyper_bit; + + reason = vmcs_read(EXI_REASON) & 0xff; + hyper_bit = hypercall_field & HYPERCALL_BIT; + if (reason == VMX_VMCALL && hyper_bit) + return true; + return false; +} + +static int handle_hypercall() +{ + ulong hypercall_no; + + hypercall_no = hypercall_field & HYPERCALL_MASK; + hypercall_field = 0; + switch (hypercall_no) { + case HYPERCALL_VMEXIT: + return VMX_TEST_VMEXIT; + default: + printf("ERROR : Invalid hypercall number : %d\n", hypercall_no); + } + return VMX_TEST_EXIT; +} + +static int exit_handler() +{ + int ret; + + current->exits++; + regs.rflags = vmcs_read(GUEST_RFLAGS); + if (is_hypercall()) + ret = handle_hypercall(); + else + ret = current->exit_handler(); + vmcs_write(GUEST_RFLAGS, regs.rflags); + switch (ret) { + case VMX_TEST_VMEXIT: + case VMX_TEST_RESUME: + return ret; + case VMX_TEST_EXIT: + break; + default: + printf("ERROR : Invalid exit_handler return val %d.\n" + , ret); + } + print_vmexit_info(); + exit(-1); + return 0; +} + +static int vmx_run() +{ + u32 ret = 0, fail = 0; + + while (1) { + asm volatile ( + "mov %%rsp, %%rsi\n\t" + "mov %2, %%rdi\n\t" + "vmwrite %%rsi, %%rdi\n\t" + + LOAD_GPR_C + "cmpl $0, %1\n\t" + "jne 1f\n\t" + LOAD_RFLAGS + "vmlaunch\n\t" + "jmp 2f\n\t" + "1: " + "vmresume\n\t" + "2: " + "setbe %0\n\t" + "vmx_return:\n\t" + SAVE_GPR_C + SAVE_RFLAGS + : "=m"(fail) + : "m"(launched), "i"(HOST_RSP) + : "rdi", "rsi", "memory", "cc" + + ); + if (fail) + ret = launched ? VMX_TEST_RESUME_ERR : + VMX_TEST_LAUNCH_ERR; + else { + launched = 1; + ret = exit_handler(); + } + if (ret != VMX_TEST_RESUME) + break; + } + launched = 0; + switch (ret) { + case VMX_TEST_VMEXIT: + return 0; + case VMX_TEST_LAUNCH_ERR: + printf("%s : vmlaunch failed.\n", __func__); + if ((!(host_rflags & X86_EFLAGS_CF) && !(host_rflags & X86_EFLAGS_ZF)) + || ((host_rflags & X86_EFLAGS_CF) && (host_rflags & X86_EFLAGS_ZF))) + printf("\tvmlaunch set wrong flags\n"); + report("test vmlaunch", 0); + break; + case VMX_TEST_RESUME_ERR: + printf("%s : vmresume failed.\n", __func__); + if ((!(host_rflags & X86_EFLAGS_CF) && !(host_rflags & X86_EFLAGS_ZF)) + || ((host_rflags & X86_EFLAGS_CF) && (host_rflags & X86_EFLAGS_ZF))) + printf("\tvmresume set wrong flags\n"); + report("test vmresume", 0); + break; + default: + printf("%s : unhandled ret from exit_handler, ret=%d.\n", __func__, ret); + break; + } + return 1; +} + +static int test_run(struct vmx_test *test) +{ + 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); + test->exits = 0; + current = test; + regs = test->guest_regs; + vmcs_write(GUEST_RFLAGS, regs.rflags | 0x2); + launched = 0; + printf("\nTest suite : %s\n", test->name); + vmx_run(); + if (vmx_off()) { + printf("%s : vmxoff failed.\n", __func__); + return 1; + } + return 0; +} + +extern struct vmx_test vmx_tests[]; + +int main(void) +{ + int i = 0; + + setup_vm(); + setup_idt(); + fails = tests = 0; + hypercall_field = 0; + + if (test_vmx_capability() != 0) { + printf("ERROR : vmx not supported, check +vmx option\n"); + goto exit; + } + init_vmx(); + /* 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; + } + test_vmxoff(); + + while (vmx_tests[++i].name != NULL) + if (test_run(&vmx_tests[i])) + goto exit; + +exit: + printf("\nSUMMARY: %d tests, %d failures\n", tests, fails); + return fails ? 1 : 0; +} diff --git a/kvm-unittest/x86/vmx_tests.c b/kvm-unittest/x86/vmx_tests.c new file mode 100644 index 0000000..c1b39f4 --- /dev/null +++ b/kvm-unittest/x86/vmx_tests.c @@ -0,0 +1,87 @@ +#include "vmx.h" + +void basic_init() +{ +} + +void basic_guest_main() +{ + /* Here is a basic guest_main, print Hello World */ + printf("\tHello World, this is null_guest_main!\n"); +} + +int basic_exit_handler() +{ + u64 guest_rip; + ulong reason; + + guest_rip = vmcs_read(GUEST_RIP); + reason = vmcs_read(EXI_REASON) & 0xff; + + switch (reason) { + case VMX_VMCALL: + print_vmexit_info(); + vmcs_write(GUEST_RIP, guest_rip + 3); + return VMX_TEST_RESUME; + default: + break; + } + printf("ERROR : Unhandled vmx exit.\n"); + print_vmexit_info(); + return VMX_TEST_EXIT; +} + +void basic_syscall_handler(u64 syscall_no) +{ +} + +void vmenter_main() +{ + u64 rax; + u64 rsp, resume_rsp; + + report("test vmlaunch", 1); + + asm volatile( + "mov %%rsp, %0\n\t" + "mov %3, %%rax\n\t" + "vmcall\n\t" + "mov %%rax, %1\n\t" + "mov %%rsp, %2\n\t" + : "=r"(rsp), "=r"(rax), "=r"(resume_rsp) + : "g"(0xABCD)); + report("test vmresume", (rax == 0xFFFF) && (rsp == resume_rsp)); +} + +int vmenter_exit_handler() +{ + u64 guest_rip; + ulong reason; + + guest_rip = vmcs_read(GUEST_RIP); + reason = vmcs_read(EXI_REASON) & 0xff; + switch (reason) { + case VMX_VMCALL: + if (regs.rax != 0xABCD) { + report("test vmresume", 0); + return VMX_TEST_VMEXIT; + } + regs.rax = 0xFFFF; + vmcs_write(GUEST_RIP, guest_rip + 3); + return VMX_TEST_RESUME; + default: + report("test vmresume", 0); + print_vmexit_info(); + } + return VMX_TEST_VMEXIT; +} + +/* name/init/guest_main/exit_handler/syscall_handler/guest_regs + basic_* just implement some basic functions */ +struct vmx_test vmx_tests[] = { + { "null", basic_init, basic_guest_main, basic_exit_handler, + basic_syscall_handler, {0} }, + { "vmenter", basic_init, vmenter_main, vmenter_exit_handler, + basic_syscall_handler, {0} }, + { NULL, NULL, NULL, NULL, NULL, {0} }, +}; diff --git a/kvm-unittest/x86/xsave.c b/kvm-unittest/x86/xsave.c new file mode 100644 index 0000000..057b0ff --- /dev/null +++ b/kvm-unittest/x86/xsave.c @@ -0,0 +1,262 @@ +#include "libcflat.h" +#include "desc.h" + +#ifdef __x86_64__ +#define uint64_t unsigned long +#else +#define uint64_t unsigned long long +#endif + +static inline void __cpuid(unsigned int *eax, unsigned int *ebx, + unsigned int *ecx, unsigned int *edx) +{ + /* ecx is often an input as well as an output. */ + asm volatile("cpuid" + : "=a" (*eax), + "=b" (*ebx), + "=c" (*ecx), + "=d" (*edx) + : "0" (*eax), "2" (*ecx)); +} + +/* + * Generic CPUID function + * clear %ecx since some cpus (Cyrix MII) do not set or clear %ecx + * resulting in stale register contents being returned. + */ +void cpuid(unsigned int op, + unsigned int *eax, unsigned int *ebx, + unsigned int *ecx, unsigned int *edx) +{ + *eax = op; + *ecx = 0; + __cpuid(eax, ebx, ecx, edx); +} + +/* Some CPUID calls want 'count' to be placed in ecx */ +void cpuid_count(unsigned int op, int count, + unsigned int *eax, unsigned int *ebx, + unsigned int *ecx, unsigned int *edx) +{ + *eax = op; + *ecx = count; + __cpuid(eax, ebx, ecx, edx); +} + +int xgetbv_checking(u32 index, u64 *result) +{ + u32 eax, edx; + + asm volatile(ASM_TRY("1f") + ".byte 0x0f,0x01,0xd0\n\t" /* xgetbv */ + "1:" + : "=a" (eax), "=d" (edx) + : "c" (index)); + *result = eax + ((u64)edx << 32); + return exception_vector(); +} + +int xsetbv_checking(u32 index, u64 value) +{ + u32 eax = value; + u32 edx = value >> 32; + + asm volatile(ASM_TRY("1f") + ".byte 0x0f,0x01,0xd1\n\t" /* xsetbv */ + "1:" + : : "a" (eax), "d" (edx), "c" (index)); + return exception_vector(); +} + +unsigned long read_cr4(void) +{ + unsigned long val; + asm volatile("mov %%cr4,%0" : "=r" (val)); + return val; +} + +int write_cr4_checking(unsigned long val) +{ + asm volatile(ASM_TRY("1f") + "mov %0,%%cr4\n\t" + "1:": : "r" (val)); + return exception_vector(); +} + +#define CPUID_1_ECX_XSAVE (1 << 26) +#define CPUID_1_ECX_OSXSAVE (1 << 27) +int check_cpuid_1_ecx(unsigned int bit) +{ + unsigned int eax, ebx, ecx, edx; + cpuid(1, &eax, &ebx, &ecx, &edx); + if (ecx & bit) + return 1; + return 0; +} + +uint64_t get_supported_xcr0(void) +{ + unsigned int eax, ebx, ecx, edx; + cpuid_count(0xd, 0, &eax, &ebx, &ecx, &edx); + printf("eax %x, ebx %x, ecx %x, edx %x\n", + eax, ebx, ecx, edx); + return eax + ((u64)edx << 32); +} + +#define X86_CR4_OSXSAVE 0x00040000 +#define XCR_XFEATURE_ENABLED_MASK 0x00000000 +#define XCR_XFEATURE_ILLEGAL_MASK 0x00000010 + +#define XSTATE_FP 0x1 +#define XSTATE_SSE 0x2 +#define XSTATE_YMM 0x4 + +static int total_tests, fail_tests; + +void pass_if(int condition) +{ + total_tests ++; + if (condition) + printf("Pass!\n"); + else { + printf("Fail!\n"); + fail_tests ++; + } +} + +void test_xsave(void) +{ + unsigned long cr4; + uint64_t supported_xcr0; + uint64_t test_bits; + u64 xcr0; + int r; + + printf("Legal instruction testing:\n"); + supported_xcr0 = get_supported_xcr0(); + printf("Supported XCR0 bits: 0x%x\n", supported_xcr0); + + printf("Check minimal XSAVE required bits: "); + test_bits = XSTATE_FP | XSTATE_SSE; + pass_if((supported_xcr0 & test_bits) == test_bits); + + printf("Set CR4 OSXSAVE: "); + cr4 = read_cr4(); + r = write_cr4_checking(cr4 | X86_CR4_OSXSAVE); + pass_if(r == 0); + + printf("Check CPUID.1.ECX.OSXSAVE - expect 1: "); + pass_if(check_cpuid_1_ecx(CPUID_1_ECX_OSXSAVE)); + + printf(" Legal tests\n"); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, XSTATE_FP): "); + test_bits = XSTATE_FP; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == 0); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, " + "XSTATE_FP | XSTATE_SSE): "); + test_bits = XSTATE_FP | XSTATE_SSE; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == 0); + printf(" xgetbv(XCR_XFEATURE_ENABLED_MASK): "); + r = xgetbv_checking(XCR_XFEATURE_ENABLED_MASK, &xcr0); + pass_if(r == 0); + printf(" Illegal tests\n"); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, 0) - expect #GP: "); + test_bits = 0; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == GP_VECTOR); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, XSTATE_SSE) " + "- expect #GP: "); + test_bits = XSTATE_SSE; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == GP_VECTOR); + if (supported_xcr0 & XSTATE_YMM) { + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, " + "XSTATE_YMM) - expect #GP: "); + test_bits = XSTATE_YMM; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == GP_VECTOR); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, " + "XSTATE_FP | XSTATE_YMM) - expect #GP: "); + test_bits = XSTATE_FP | XSTATE_YMM; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == GP_VECTOR); + } + printf(" xsetbv(XCR_XFEATURE_ILLEGAL_MASK, XSTATE_FP) " + "- expect #GP: "); + test_bits = XSTATE_SSE; + r = xsetbv_checking(XCR_XFEATURE_ILLEGAL_MASK, test_bits); + pass_if(r == GP_VECTOR); + printf(" xgetbv(XCR_XFEATURE_ILLEGAL_MASK, XSTATE_FP) " + "- expect #GP: "); + test_bits = XSTATE_SSE; + r = xsetbv_checking(XCR_XFEATURE_ILLEGAL_MASK, test_bits); + pass_if(r == GP_VECTOR); + + printf("Unset CR4 OSXSAVE: "); + cr4 &= ~X86_CR4_OSXSAVE; + r = write_cr4_checking(cr4); + pass_if(r == 0); + printf("Check CPUID.1.ECX.OSXSAVE - expect 0: "); + pass_if(check_cpuid_1_ecx(CPUID_1_ECX_OSXSAVE) == 0); + printf(" Illegal tests:\n"); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, XSTATE_FP) - expect #UD: "); + test_bits = XSTATE_FP; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == UD_VECTOR); + printf(" xsetbv(XCR_XFEATURE_ENABLED_MASK, " + "XSTATE_FP | XSTATE_SSE) - expect #UD: "); + test_bits = XSTATE_FP | XSTATE_SSE; + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, test_bits); + pass_if(r == UD_VECTOR); + printf(" Illegal tests:\n"); + printf(" xgetbv(XCR_XFEATURE_ENABLED_MASK) - expect #UD: "); + r = xgetbv_checking(XCR_XFEATURE_ENABLED_MASK, &xcr0); + pass_if(r == UD_VECTOR); +} + +void test_no_xsave(void) +{ + unsigned long cr4; + u64 xcr0; + int r; + + printf("Check CPUID.1.ECX.OSXSAVE - expect 0: "); + pass_if(check_cpuid_1_ecx(CPUID_1_ECX_OSXSAVE) == 0); + + printf("Illegal instruction testing:\n"); + + printf("Set OSXSAVE in CR4 - expect #GP: "); + cr4 = read_cr4(); + r = write_cr4_checking(cr4 | X86_CR4_OSXSAVE); + pass_if(r == GP_VECTOR); + + printf("Execute xgetbv - expect #UD: "); + r = xgetbv_checking(XCR_XFEATURE_ENABLED_MASK, &xcr0); + pass_if(r == UD_VECTOR); + + printf("Execute xsetbv - expect #UD: "); + r = xsetbv_checking(XCR_XFEATURE_ENABLED_MASK, 0x3); + pass_if(r == UD_VECTOR); +} + +int main(void) +{ + setup_idt(); + if (check_cpuid_1_ecx(CPUID_1_ECX_XSAVE)) { + printf("CPU has XSAVE feature\n"); + test_xsave(); + } else { + printf("CPU don't has XSAVE feature\n"); + test_no_xsave(); + } + printf("Total test: %d\n", total_tests); + if (fail_tests == 0) + printf("ALL PASS!\n"); + else { + printf("Fail %d tests.\n", fail_tests); + return 1; + } + return 0; +} diff --git a/kvm-unittest/.gitignore b/kvm-unittest/.gitignore new file mode 100644 index 0000000..ed857b7 --- /dev/null +++ b/kvm-unittest/.gitignore @@ -0,0 +1,9 @@ +.gdbinit +*.a +*.d +*.o +*.flat +.pc +patches +.stgit-* +cscope.* diff --git a/kvm-unittest/COPYRIGHT b/kvm-unittest/COPYRIGHT new file mode 100644 index 0000000..d35649c --- /dev/null +++ b/kvm-unittest/COPYRIGHT @@ -0,0 +1,4 @@ +Copyright (C) 2006 Qumranet. + +The files in this directory and its subdirectories are licensed under the +GNU LGPL, version 2. diff --git a/kvm-unittest/Makefile b/kvm-unittest/Makefile new file mode 100644 index 0000000..b6e8759 --- /dev/null +++ b/kvm-unittest/Makefile @@ -0,0 +1,60 @@ + +include config.mak + +DESTDIR := $(PREFIX)/share/qemu/tests + +.PHONY: arch_clean clean + +#make sure env CFLAGS variable is not used +CFLAGS = -g + +libgcc := $(shell $(CC) --print-libgcc-file-name) + +libcflat := lib/libcflat.a +cflatobjs := \ + lib/panic.o \ + lib/printf.o \ + lib/string.o +cflatobjs += lib/argv.o + +#include architecure specific make rules +include config-$(ARCH).mak + +# cc-option +# Usage: OP_CFLAGS+=$(call cc-option, -falign-functions=0, -malign-functions=0) + +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 += -O1 +CFLAGS += $(autodepend-flags) -g -fomit-frame-pointer -Wall +CFLAGS += $(call cc-option, -fno-stack-protector, "") +CFLAGS += $(call cc-option, -fno-stack-protector-all, "") +CFLAGS += -I. + +CXXFLAGS += $(CFLAGS) + +autodepend-flags = -MMD -MF $(dir $*).$(notdir $*).d + +LDFLAGS += $(CFLAGS) +LDFLAGS += -pthread -lrt + +kvmtrace_objs= kvmtrace.o + +kvmtrace: $(kvmtrace_objs) + $(CC) $(LDFLAGS) $^ -o $@ + +$(libcflat): $(cflatobjs) + $(AR) rcs $@ $^ + +%.o: %.S + $(CC) $(CFLAGS) -c -nostdlib -o $@ $< + +-include .*.d */.*.d */*/.*.d + +install: + mkdir -p $(DESTDIR) + install $(tests_and_config) $(DESTDIR) + +clean: arch_clean + $(RM) kvmtrace *.o *.a .*.d $(libcflat) $(cflatobjs) diff --git a/kvm-unittest/README b/kvm-unittest/README new file mode 100644 index 0000000..db525e3 --- /dev/null +++ b/kvm-unittest/README @@ -0,0 +1,36 @@ +This directory contains sources for a kvm test suite. + +Tests for x86 architecture are run as kernel images for qemu that supports multiboot format. +Tests uses an infrastructure called from the bios code. The infrastructure initialize the system/cpu's, +switch to long-mode and calls the 'main' function of the individual test. +Tests uses a qemu's virtual test device, named testdev, for services like printing, exiting, query memory size etc. +See file testdev.txt for more details. + +To create the tests' images just type 'make' in this directory. +Tests' images created in .//*.flat + +An example of a test invocation: +Using qemu-kvm: + +qemu-kvm -device testdev,chardev=testlog -chardev file,id=testlog,path=msr.out -serial stdio -kernel ./x86/msr.flat +This invocation runs the msr test case. The test outputs to stdio. + +Using qemu (supported since qemu 1.3): +qemu-system-x86_64 -enable-kvm -device pc-testdev -serial stdio -device isa-debug-exit,iobase=0xf4,iosize=0x4 -kernel ./x86/msr.flat + +Or use a runner script to detect the correct invocation: +./x86-run ./x86/msr.flat +To select a specific qemu binary, specify the QEMU= environment: +QEMU=/tmp/qemu/x86_64-softmmu/qemu-system-x86_64 ./x86-run ./x86/msr.flat + +The exit status of the binary (and the script) is inconsistent: with +qemu-system, after the unittest is done, the exit status of qemu is 1, +different from the 'old style' qemu-kvm, whose exit status in successful +completion is 0. + +Directory structure: +.: Makefile and config files for the tests +./lib: general services for the tests +./lib/: architecture dependent services for the tests +./: the sources of the tests and the created objects/images + diff --git a/kvm-unittest/api/api-sample.cc b/kvm-unittest/api/api-sample.cc new file mode 100644 index 0000000..524ad7b --- /dev/null +++ b/kvm-unittest/api/api-sample.cc @@ -0,0 +1,30 @@ + +#include "api/kvmxx.hh" +#include "api/identity.hh" +#include "api/exception.hh" +#include "stdio.h" + +static int global = 0; + +static void set_global() +{ + global = 1; +} + +int test_main(int ac, char** av) +{ + kvm::system system; + kvm::vm vm(system); + mem_map memmap(vm); + identity::vm ident_vm(vm, memmap); + kvm::vcpu vcpu(vm, 0); + identity::vcpu thread(vcpu, set_global); + vcpu.run(); + printf("global %d\n", global); + return global == 1 ? 0 : 1; +} + +int main(int ac, char** av) +{ + return try_main(test_main, ac, av); +} diff --git a/kvm-unittest/api/dirty-log-perf.cc b/kvm-unittest/api/dirty-log-perf.cc new file mode 100644 index 0000000..7f2488e --- /dev/null +++ b/kvm-unittest/api/dirty-log-perf.cc @@ -0,0 +1,144 @@ +#include "kvmxx.hh" +#include "memmap.hh" +#include "identity.hh" +#include +#include +#include +#include + +namespace { + +const int page_size = 4096; +int64_t nr_total_pages = 256 * 1024; +int64_t nr_slot_pages = 256 * 1024; + +// Return the current time in nanoseconds. +uint64_t time_ns() +{ + struct timespec ts; + + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * (uint64_t)1000000000 + ts.tv_nsec; +} + +// Update nr_to_write pages selected from nr_pages pages. +void write_mem(void* slot_head, int64_t nr_to_write, int64_t nr_pages) +{ + char* var = static_cast(slot_head); + int64_t interval = nr_pages / nr_to_write; + + for (int64_t i = 0; i < nr_to_write; ++i) { + ++(*var); + var += interval * page_size; + } +} + +using boost::ref; +using std::tr1::bind; + +// Let the guest update nr_to_write pages selected from nr_pages pages. +void do_guest_write(kvm::vcpu& vcpu, void* slot_head, + int64_t nr_to_write, int64_t nr_pages) +{ + identity::vcpu guest_write_thread(vcpu, bind(write_mem, ref(slot_head), + nr_to_write, nr_pages)); + vcpu.run(); +} + +// Check how long it takes to update dirty log. +void check_dirty_log(kvm::vcpu& vcpu, mem_slot& slot, void* slot_head) +{ + slot.set_dirty_logging(true); + slot.update_dirty_log(); + + for (int64_t i = 1; i <= nr_slot_pages; i *= 2) { + do_guest_write(vcpu, slot_head, i, nr_slot_pages); + + uint64_t start_ns = time_ns(); + slot.update_dirty_log(); + uint64_t end_ns = time_ns(); + + printf("get dirty log: %10lld ns for %10lld dirty pages\n", + end_ns - start_ns, i); + } + + slot.set_dirty_logging(false); +} + +} + +void parse_options(int ac, char **av) +{ + int opt; + char *endptr; + + while ((opt = getopt(ac, av, "n:m:")) != -1) { + switch (opt) { + case 'n': + errno = 0; + nr_slot_pages = strtol(optarg, &endptr, 10); + if (errno || endptr == optarg) { + printf("dirty-log-perf: Invalid number: -n %s\n", optarg); + exit(1); + } + if (*endptr == 'k' || *endptr == 'K') { + nr_slot_pages *= 1024; + } + break; + case 'm': + errno = 0; + nr_total_pages = strtol(optarg, &endptr, 10); + if (errno || endptr == optarg) { + printf("dirty-log-perf: Invalid number: -m %s\n", optarg); + exit(1); + } + if (*endptr == 'k' || *endptr == 'K') { + nr_total_pages *= 1024; + } + break; + default: + printf("dirty-log-perf: Invalid option\n"); + exit(1); + } + } + + if (nr_slot_pages > nr_total_pages) { + printf("dirty-log-perf: Invalid setting: slot %lld > mem %lld\n", + nr_slot_pages, nr_total_pages); + exit(1); + } + printf("dirty-log-perf: %lld slot pages / %lld mem pages\n", + nr_slot_pages, nr_total_pages); +} + +int main(int ac, char **av) +{ + kvm::system sys; + kvm::vm vm(sys); + mem_map memmap(vm); + + parse_options(ac, av); + + void* mem_head; + int64_t mem_size = nr_total_pages * page_size; + if (posix_memalign(&mem_head, page_size, mem_size)) { + printf("dirty-log-perf: Could not allocate guest memory.\n"); + exit(1); + } + uint64_t mem_addr = reinterpret_cast(mem_head); + + identity::hole hole(mem_head, mem_size); + identity::vm ident_vm(vm, memmap, hole); + kvm::vcpu vcpu(vm, 0); + + uint64_t slot_size = nr_slot_pages * page_size; + uint64_t next_size = mem_size - slot_size; + uint64_t next_addr = mem_addr + slot_size; + mem_slot slot(memmap, mem_addr, slot_size, mem_head); + mem_slot other_slot(memmap, next_addr, next_size, (void *)next_addr); + + // pre-allocate shadow pages + do_guest_write(vcpu, mem_head, nr_total_pages, nr_total_pages); + check_dirty_log(vcpu, slot, mem_head); + return 0; +} diff --git a/kvm-unittest/api/dirty-log.cc b/kvm-unittest/api/dirty-log.cc new file mode 100644 index 0000000..1e4ef9e --- /dev/null +++ b/kvm-unittest/api/dirty-log.cc @@ -0,0 +1,78 @@ +#include "kvmxx.hh" +#include "memmap.hh" +#include "identity.hh" +#include +#include +#include + +namespace { + +void delay_loop(unsigned n) +{ + for (unsigned i = 0; i < n; ++i) { + asm volatile("pause"); + } + } + +void write_mem(volatile bool& running, volatile int* shared_var) +{ + while (running) { + ++*shared_var; + delay_loop(1000); + } +} + +void check_dirty_log(mem_slot& slot, + volatile bool& running, + volatile int* shared_var, + int& nr_fail) +{ + uint64_t shared_var_gpa = reinterpret_cast(shared_var); + slot.set_dirty_logging(true); + slot.update_dirty_log(); + for (int i = 0; i < 10000000; ++i) { + int sample1 = *shared_var; + delay_loop(600); + int sample2 = *shared_var; + slot.update_dirty_log(); + if (!slot.is_dirty(shared_var_gpa) && sample1 != sample2) { + ++nr_fail; + } + } + running = false; + slot.set_dirty_logging(false); +} + +} + +using boost::ref; +using std::tr1::bind; + +int main(int ac, char **av) +{ + kvm::system sys; + kvm::vm vm(sys); + mem_map memmap(vm); + void* logged_slot_virt; + posix_memalign(&logged_slot_virt, 4096, 4096); + int* shared_var = static_cast(logged_slot_virt); + identity::hole hole(logged_slot_virt, 4096); + identity::vm ident_vm(vm, memmap, hole); + kvm::vcpu vcpu(vm, 0); + bool running = true; + int nr_fail = 0; + mem_slot logged_slot(memmap, + reinterpret_cast(logged_slot_virt), + 4096, logged_slot_virt); + boost::thread host_poll_thread(check_dirty_log, ref(logged_slot), + ref(running), + ref(shared_var), ref(nr_fail)); + identity::vcpu guest_write_thread(vcpu, + bind(write_mem, + ref(running), + ref(shared_var))); + vcpu.run(); + host_poll_thread.join(); + printf("Dirty bitmap failures: %d\n", nr_fail); + return nr_fail == 0 ? 0 : 1; +} diff --git a/kvm-unittest/api/exception.cc b/kvm-unittest/api/exception.cc new file mode 100644 index 0000000..910bdff --- /dev/null +++ b/kvm-unittest/api/exception.cc @@ -0,0 +1,33 @@ +#include "exception.hh" +#include +#include + +errno_exception::errno_exception(int errno) + : _errno(errno) +{ +} + +int errno_exception::errno() const +{ + return _errno; +} + +const char *errno_exception::what() +{ + std::snprintf(_buf, sizeof _buf, "error: %s (%d)", + std::strerror(_errno), _errno); + return _buf; +} + +int try_main(int (*main)(int argc, char** argv), int argc, char** argv, + int ret_on_exception) +{ + try { + return main(argc, argv); + } catch (std::exception& e) { + std::fprintf(stderr, "exception: %s\n", e.what()); + } catch (...) { + std::fprintf(stderr, "unknown exception\n"); + } + return ret_on_exception; +} diff --git a/kvm-unittest/api/exception.hh b/kvm-unittest/api/exception.hh new file mode 100644 index 0000000..f78d9a1 --- /dev/null +++ b/kvm-unittest/api/exception.hh @@ -0,0 +1,19 @@ +#ifndef EXCEPTION_HH +#define EXCEPTION_HH + +#include + +class errno_exception : public std::exception { +public: + explicit errno_exception(int err_no); + int errno() const; + virtual const char *what(); +private: + int _errno; + char _buf[1000]; +}; + +int try_main(int (*main)(int argc, char** argv), int argc, char** argv, + int ret_on_exception = 127); + +#endif diff --git a/kvm-unittest/api/identity.cc b/kvm-unittest/api/identity.cc new file mode 100644 index 0000000..e04231b --- /dev/null +++ b/kvm-unittest/api/identity.cc @@ -0,0 +1,95 @@ + +#include "identity.hh" +#include + +namespace identity { + +typedef unsigned long ulong; + +hole::hole() + : address(), size() +{ +} + +hole::hole(void* address, size_t size) + : address(address), size(size) +{ +} + +vm::vm(kvm::vm& vm, mem_map& mmap, hole h) +{ + uint64_t hole_gpa = reinterpret_cast(h.address); + char* hole_hva = static_cast(h.address); + if (h.address) { + _slots.push_back(mem_slot_ptr(new mem_slot(mmap, 0, hole_gpa, NULL))); + } + uint64_t hole_end = hole_gpa + h.size; + uint64_t end = 3U << 30; + _slots.push_back(mem_slot_ptr(new mem_slot(mmap, hole_end, + end - hole_end, + hole_hva + h.size))); + vm.set_tss_addr(3UL << 30); +} + +void vcpu::setup_sregs() +{ + kvm_sregs sregs = { }; + kvm_segment dseg = { }; + dseg.base = 0; dseg.limit = -1U; dseg.type = 3; dseg.present = 1; + dseg.dpl = 3; dseg.db = 1; dseg.s = 1; dseg.l = 0; dseg.g = 1; + kvm_segment cseg = dseg; + cseg.type = 11; + + sregs.cs = cseg; asm ("mov %%cs, %0" : "=rm"(sregs.cs.selector)); + sregs.ds = dseg; asm ("mov %%ds, %0" : "=rm"(sregs.ds.selector)); + sregs.es = dseg; asm ("mov %%es, %0" : "=rm"(sregs.es.selector)); + sregs.fs = dseg; asm ("mov %%fs, %0" : "=rm"(sregs.fs.selector)); + sregs.gs = dseg; asm ("mov %%gs, %0" : "=rm"(sregs.gs.selector)); + sregs.ss = dseg; asm ("mov %%ss, %0" : "=rm"(sregs.ss.selector)); + + uint32_t gsbase; + asm ("mov %%gs:0, %0" : "=r"(gsbase)); + sregs.gs.base = gsbase; + + sregs.tr.base = reinterpret_cast(&*_stack.begin()); + sregs.tr.type = 11; + sregs.tr.s = 0; + sregs.tr.present = 1; + + sregs.cr0 = 0x11; /* PE, ET, !PG */ + sregs.cr4 = 0; + sregs.efer = 0; + sregs.apic_base = 0xfee00000; + _vcpu.set_sregs(sregs); +} + +void vcpu::thunk(vcpu* zis) +{ + zis->_guest_func(); + asm volatile("outb %%al, %%dx" : : "a"(0), "d"(0)); +} + +void vcpu::setup_regs() +{ + kvm_regs regs = {}; + regs.rflags = 0x3202; + regs.rsp = reinterpret_cast(&*_stack.end()); + regs.rsp &= ~15UL; + ulong* sp = reinterpret_cast(regs.rsp); + *--sp = reinterpret_cast((char*)this); + *--sp = 0; + regs.rsp = reinterpret_cast(sp); + regs.rip = reinterpret_cast(&vcpu::thunk); + printf("rip %llx\n", regs.rip); + _vcpu.set_regs(regs); +} + +vcpu::vcpu(kvm::vcpu& vcpu, std::tr1::function guest_func, + unsigned long stack_size) + : _vcpu(vcpu), _guest_func(guest_func), _stack(stack_size) +{ + setup_sregs(); + setup_regs(); +} + +} diff --git a/kvm-unittest/api/identity.hh b/kvm-unittest/api/identity.hh new file mode 100644 index 0000000..4491043 --- /dev/null +++ b/kvm-unittest/api/identity.hh @@ -0,0 +1,43 @@ +#ifndef API_IDENTITY_HH +#define API_IDENTITY_HH + +#include "kvmxx.hh" +#include "memmap.hh" +#include +#include +#include + +namespace identity { + +struct hole { + hole(); + hole(void* address, size_t size); + void* address; + size_t size; +}; + +class vm { +public: + vm(kvm::vm& vm, mem_map& mmap, hole address_space_hole = hole()); +private: + typedef std::tr1::shared_ptr mem_slot_ptr; + std::vector _slots; +}; + +class vcpu { +public: + vcpu(kvm::vcpu& vcpu, std::tr1::function guest_func, + unsigned long stack_size = 256 * 1024); +private: + static void thunk(vcpu* vcpu); + void setup_regs(); + void setup_sregs(); +private: + kvm::vcpu& _vcpu; + std::tr1::function _guest_func; + std::vector _stack; +}; + +} + +#endif diff --git a/kvm-unittest/api/kvmxx.cc b/kvm-unittest/api/kvmxx.cc new file mode 100644 index 0000000..7ebebb5 --- /dev/null +++ b/kvm-unittest/api/kvmxx.cc @@ -0,0 +1,194 @@ +#include "kvmxx.hh" +#include "exception.hh" +#include +#include +#include +#include +#include +#include + +namespace kvm { + +static long check_error(long r) +{ + if (r == -1) { + throw errno_exception(errno); + } + return r; +} + +fd::fd(int fd) + : _fd(fd) +{ +} + +fd::fd(const fd& other) + : _fd(::dup(other._fd)) +{ + check_error(_fd); +} + +fd::fd(std::string device_node, int flags) + : _fd(::open(device_node.c_str(), flags)) +{ + check_error(_fd); +} + +long fd::ioctl(unsigned nr, long arg) +{ + return check_error(::ioctl(_fd, nr, arg)); +} + +vcpu::vcpu(vm& vm, int id) + : _vm(vm), _fd(vm._fd.ioctl(KVM_CREATE_VCPU, id)), _shared(NULL) + , _mmap_size(_vm._system._fd.ioctl(KVM_GET_VCPU_MMAP_SIZE, 0)) + +{ + kvm_run *shared = static_cast(::mmap(NULL, _mmap_size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + _fd.get(), 0)); + if (shared == MAP_FAILED) { + throw errno_exception(errno); + } + _shared = shared; +} + +vcpu::~vcpu() +{ + munmap(_shared, _mmap_size); +} + +void vcpu::run() +{ + _fd.ioctl(KVM_RUN, 0); +} + +kvm_regs vcpu::regs() +{ + kvm_regs regs; + _fd.ioctlp(KVM_GET_REGS, ®s); + return regs; +} + +void vcpu::set_regs(const kvm_regs& regs) +{ + _fd.ioctlp(KVM_SET_REGS, const_cast(®s)); +} + +kvm_sregs vcpu::sregs() +{ + kvm_sregs sregs; + _fd.ioctlp(KVM_GET_SREGS, &sregs); + return sregs; +} + +void vcpu::set_sregs(const kvm_sregs& sregs) +{ + _fd.ioctlp(KVM_SET_SREGS, const_cast(&sregs)); +} + +class vcpu::kvm_msrs_ptr { +public: + explicit kvm_msrs_ptr(size_t nmsrs); + ~kvm_msrs_ptr() { ::free(_kvm_msrs); } + kvm_msrs* operator->() { return _kvm_msrs; } + kvm_msrs* get() { return _kvm_msrs; } +private: + kvm_msrs* _kvm_msrs; +}; + +vcpu::kvm_msrs_ptr::kvm_msrs_ptr(size_t nmsrs) + : _kvm_msrs(0) +{ + size_t size = sizeof(kvm_msrs) + sizeof(kvm_msr_entry) * nmsrs; + _kvm_msrs = static_cast(::malloc(size)); + if (!_kvm_msrs) { + throw std::bad_alloc(); + } +} + +std::vector vcpu::msrs(std::vector indices) +{ + kvm_msrs_ptr msrs(indices.size()); + msrs->nmsrs = indices.size(); + for (unsigned i = 0; i < msrs->nmsrs; ++i) { + msrs->entries[i].index = indices[i]; + } + _fd.ioctlp(KVM_GET_MSRS, msrs.get()); + return std::vector(msrs->entries, + msrs->entries + msrs->nmsrs); +} + +void vcpu::set_msrs(const std::vector& msrs) +{ + kvm_msrs_ptr _msrs(msrs.size()); + _msrs->nmsrs = msrs.size(); + std::copy(msrs.begin(), msrs.end(), _msrs->entries); + _fd.ioctlp(KVM_SET_MSRS, _msrs.get()); +} + +void vcpu::set_debug(uint64_t dr[8], bool enabled, bool singlestep) +{ + kvm_guest_debug gd; + + gd.control = 0; + if (enabled) { + gd.control |= KVM_GUESTDBG_ENABLE; + } + if (singlestep) { + gd.control |= KVM_GUESTDBG_SINGLESTEP; + } + for (int i = 0; i < 8; ++i) { + gd.arch.debugreg[i] = dr[i]; + } + _fd.ioctlp(KVM_SET_GUEST_DEBUG, &gd); +} + +vm::vm(system& system) + : _system(system), _fd(system._fd.ioctl(KVM_CREATE_VM, 0)) +{ +} + +void vm::set_memory_region(int slot, void *addr, uint64_t gpa, size_t len, + uint32_t flags) +{ + struct kvm_userspace_memory_region umr; + + umr.slot = slot; + umr.flags = flags; + umr.guest_phys_addr = gpa; + umr.memory_size = len; + umr.userspace_addr = reinterpret_cast(addr); + _fd.ioctlp(KVM_SET_USER_MEMORY_REGION, &umr); +} + +void vm::get_dirty_log(int slot, void *log) +{ + struct kvm_dirty_log kdl; + kdl.slot = slot; + kdl.dirty_bitmap = log; + _fd.ioctlp(KVM_GET_DIRTY_LOG, &kdl); +} + +void vm::set_tss_addr(uint32_t addr) +{ + _fd.ioctl(KVM_SET_TSS_ADDR, addr); +} + +system::system(std::string device_node) + : _fd(device_node, O_RDWR) +{ +} + +bool system::check_extension(int extension) +{ + return _fd.ioctl(KVM_CHECK_EXTENSION, extension); +} + +int system::get_extension_int(int extension) +{ + return _fd.ioctl(KVM_CHECK_EXTENSION, extension); +} + +}; diff --git a/kvm-unittest/api/kvmxx.hh b/kvm-unittest/api/kvmxx.hh new file mode 100644 index 0000000..1dcb41d --- /dev/null +++ b/kvm-unittest/api/kvmxx.hh @@ -0,0 +1,85 @@ +#ifndef KVMXX_H +#define KVMXX_H + +#include +#include +#include +#include +#include +#include +#include + +namespace kvm { + +class system; +class vm; +class vcpu; +class fd; + +class fd { +public: + explicit fd(int n); + explicit fd(std::string path, int flags); + fd(const fd& other); + ~fd() { ::close(_fd); } + int get() { return _fd; } + long ioctl(unsigned nr, long arg); + long ioctlp(unsigned nr, void *arg) { + return ioctl(nr, reinterpret_cast(arg)); + } +private: + int _fd; +}; + +class vcpu { +public: + vcpu(vm& vm, int fd); + ~vcpu(); + void run(); + kvm_run *shared(); + kvm_regs regs(); + void set_regs(const kvm_regs& regs); + kvm_sregs sregs(); + void set_sregs(const kvm_sregs& sregs); + std::vector msrs(std::vector indices); + void set_msrs(const std::vector& msrs); + void set_debug(uint64_t dr[8], bool enabled, bool singlestep); +private: + class kvm_msrs_ptr; +private: + vm& _vm; + fd _fd; + kvm_run *_shared; + unsigned _mmap_size; + friend class vm; +}; + +class vm { +public: + explicit vm(system& system); + void set_memory_region(int slot, void *addr, uint64_t gpa, size_t len, + uint32_t flags = 0); + void get_dirty_log(int slot, void *log); + void set_tss_addr(uint32_t addr); + system& sys() { return _system; } +private: + system& _system; + fd _fd; + friend class system; + friend class vcpu; +}; + +class system { +public: + explicit system(std::string device_node = "/dev/kvm"); + bool check_extension(int extension); + int get_extension_int(int extension); +private: + fd _fd; + friend class vcpu; + friend class vm; +}; + +}; + +#endif diff --git a/kvm-unittest/api/memmap.cc b/kvm-unittest/api/memmap.cc new file mode 100644 index 0000000..c625852 --- /dev/null +++ b/kvm-unittest/api/memmap.cc @@ -0,0 +1,76 @@ + +#include "memmap.hh" + +mem_slot::mem_slot(mem_map& map, uint64_t gpa, uint64_t size, void* hva) + : _map(map) + , _slot(map._free_slots.top()) + , _gpa(gpa) + , _size(size) + , _hva(hva) + , _dirty_log_enabled(false) + , _log() +{ + map._free_slots.pop(); + update(); +} + +mem_slot::~mem_slot() +{ + _size = 0; + try { + update(); + _map._free_slots.push(_slot); + } catch (...) { + // can't do much if we can't undo slot registration - leak the slot + } +} + +void mem_slot::set_dirty_logging(bool enabled) +{ + if (_dirty_log_enabled != enabled) { + _dirty_log_enabled = enabled; + if (enabled) { + int logsize = ((_size >> 12) + bits_per_word - 1) / bits_per_word; + _log.resize(logsize); + } else { + _log.resize(0); + } + update(); + } +} + +void mem_slot::update() +{ + uint32_t flags = 0; + if (_dirty_log_enabled) { + flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + _map._vm.set_memory_region(_slot, _hva, _gpa, _size, flags); +} + +bool mem_slot::dirty_logging() const +{ + return _dirty_log_enabled; +} + +void mem_slot::update_dirty_log() +{ + _map._vm.get_dirty_log(_slot, &_log[0]); +} + +bool mem_slot::is_dirty(uint64_t gpa) const +{ + uint64_t pagenr = (gpa - _gpa) >> 12; + ulong wordnr = pagenr / bits_per_word; + ulong bit = 1ULL << (pagenr % bits_per_word); + return _log[wordnr] & bit; +} + +mem_map::mem_map(kvm::vm& vm) + : _vm(vm) +{ + int nr_slots = vm.sys().get_extension_int(KVM_CAP_NR_MEMSLOTS); + for (int i = 0; i < nr_slots; ++i) { + _free_slots.push(i); + } +} diff --git a/kvm-unittest/api/memmap.hh b/kvm-unittest/api/memmap.hh new file mode 100644 index 0000000..59ec619 --- /dev/null +++ b/kvm-unittest/api/memmap.hh @@ -0,0 +1,43 @@ +#ifndef MEMMAP_HH +#define MEMMAP_HH + +#include "kvmxx.hh" +#include +#include +#include + +class mem_map; +class mem_slot; + +class mem_slot { +public: + mem_slot(mem_map& map, uint64_t gpa, uint64_t size, void *hva); + ~mem_slot(); + void set_dirty_logging(bool enabled); + bool dirty_logging() const; + void update_dirty_log(); + bool is_dirty(uint64_t gpa) const; +private: + void update(); +private: + typedef unsigned long ulong; + static const int bits_per_word = sizeof(ulong) * 8; + mem_map& _map; + int _slot; + uint64_t _gpa; + uint64_t _size; + void *_hva; + bool _dirty_log_enabled; + std::vector _log; +}; + +class mem_map { +public: + mem_map(kvm::vm& vm); +private: + kvm::vm& _vm; + std::stack _free_slots; + friend class mem_slot; +}; + +#endif diff --git a/kvm-unittest/config-i386.mak b/kvm-unittest/config-i386.mak new file mode 100644 index 0000000..de52f3d --- /dev/null +++ b/kvm-unittest/config-i386.mak @@ -0,0 +1,13 @@ +TEST_DIR=x86 +cstart.o = $(TEST_DIR)/cstart.o +bits = 32 +ldarch = elf32-i386 +CFLAGS += -D__i386__ +CFLAGS += -I $(KERNELDIR)/include + +tests = $(TEST_DIR)/taskswitch.flat $(TEST_DIR)/taskswitch2.flat + +include config-x86-common.mak + +$(TEST_DIR)/taskswitch.elf: $(cstart.o) $(TEST_DIR)/taskswitch.o +$(TEST_DIR)/taskswitch2.elf: $(cstart.o) $(TEST_DIR)/taskswitch2.o diff --git a/kvm-unittest/config-ia64.mak b/kvm-unittest/config-ia64.mak new file mode 100644 index 0000000..d9350fc --- /dev/null +++ b/kvm-unittest/config-ia64.mak @@ -0,0 +1,7 @@ +bits = 64 +CFLAGS += -m64 +CFLAGS += -D__ia64__ +CFLAGS += -I../include/ia64 + +all: + diff --git a/kvm-unittest/config-powerpc-440.mak b/kvm-unittest/config-powerpc-440.mak new file mode 100644 index 0000000..bb85971 --- /dev/null +++ b/kvm-unittest/config-powerpc-440.mak @@ -0,0 +1,15 @@ + + +# for some reason binutils hates tlbsx unless we say we're 405 :( +CFLAGS += -Wa,-m405 -I lib/powerpc/44x + +cflatobjs += \ + lib/powerpc/44x/map.o \ + lib/powerpc/44x/tlbwe.o \ + lib/powerpc/44x/timebase.o + +simpletests += \ + powerpc/44x/tlbsx.bin \ + powerpc/44x/tlbwe_16KB.bin \ + powerpc/44x/tlbwe_hole.bin \ + powerpc/44x/tlbwe.bin diff --git a/kvm-unittest/config-powerpc.mak b/kvm-unittest/config-powerpc.mak new file mode 100644 index 0000000..d053569 --- /dev/null +++ b/kvm-unittest/config-powerpc.mak @@ -0,0 +1,39 @@ +CFLAGS += -I../include/powerpc +CFLAGS += -Wa,-mregnames -I lib +CFLAGS += -ffreestanding + +cstart := powerpc/cstart.o + +cflatobjs += \ + lib/powerpc/io.o + +$(libcflat): LDFLAGS += -nostdlib + +# these tests do not use libcflat +simpletests := \ + powerpc/spin.bin \ + powerpc/io.bin \ + powerpc/sprg.bin + +# theses tests use cstart.o, libcflat, and libgcc +tests := \ + powerpc/exit.bin \ + powerpc/helloworld.bin + +include config-powerpc-$(PROCESSOR).mak + + +all: kvmtrace kvmctl $(libcflat) $(simpletests) $(tests) + +$(simpletests): %.bin: %.o + $(CC) -nostdlib $^ -Wl,-T,flat.lds -o $@ + +$(tests): %.bin: $(cstart) %.o $(libcflat) + $(CC) -nostdlib $^ $(libgcc) -Wl,-T,flat.lds -o $@ + +kvmctl_objs = main-ppc.o iotable.o ../libkvm/libkvm.a + +arch_clean: + $(RM) $(simpletests) $(tests) $(cstart) + $(RM) $(patsubst %.bin, %.elf, $(simpletests) $(tests)) + $(RM) $(patsubst %.bin, %.o, $(simpletests) $(tests)) diff --git a/kvm-unittest/config-x86-common.mak b/kvm-unittest/config-x86-common.mak new file mode 100644 index 0000000..bf88c67 --- /dev/null +++ b/kvm-unittest/config-x86-common.mak @@ -0,0 +1,122 @@ +#This is a make file with common rules for both x86 & x86-64 + +CFLAGS += -I../include/x86 + +all: test_cases + +cflatobjs += \ + lib/x86/io.o \ + lib/x86/smp.o + +cflatobjs += lib/x86/vm.o +cflatobjs += lib/x86/fwcfg.o +cflatobjs += lib/x86/apic.o +cflatobjs += lib/x86/atomic.o +cflatobjs += lib/x86/desc.o +cflatobjs += lib/x86/isr.o +cflatobjs += lib/x86/pci.o + +$(libcflat): LDFLAGS += -nostdlib +$(libcflat): CFLAGS += -ffreestanding -I lib + +CFLAGS += -m$(bits) + +libgcc := $(shell $(CC) -m$(bits) --print-libgcc-file-name) + +FLATLIBS = lib/libcflat.a $(libgcc) +%.elf: %.o $(FLATLIBS) flat.lds + $(CC) $(CFLAGS) -nostdlib -o $@ -Wl,-T,flat.lds $(filter %.o, $^) $(FLATLIBS) + +%.flat: %.elf + objcopy -O elf32-i386 $^ $@ + +tests-common = $(TEST_DIR)/vmexit.flat $(TEST_DIR)/tsc.flat \ + $(TEST_DIR)/smptest.flat $(TEST_DIR)/port80.flat \ + $(TEST_DIR)/realmode.flat $(TEST_DIR)/msr.flat \ + $(TEST_DIR)/hypercall.flat $(TEST_DIR)/sieve.flat \ + $(TEST_DIR)/kvmclock_test.flat $(TEST_DIR)/eventinj.flat \ + $(TEST_DIR)/s3.flat $(TEST_DIR)/pmu.flat \ + $(TEST_DIR)/tsc_adjust.flat $(TEST_DIR)/asyncpf.flat \ + $(TEST_DIR)/init.flat + +ifdef API +tests-common += api/api-sample +tests-common += api/dirty-log +tests-common += api/dirty-log-perf +endif + +tests_and_config = $(TEST_DIR)/*.flat $(TEST_DIR)/unittests.cfg + +test_cases: $(tests-common) $(tests) + +$(TEST_DIR)/%.o: CFLAGS += -std=gnu99 -ffreestanding -I lib -I lib/x86 + +$(TEST_DIR)/access.elf: $(cstart.o) $(TEST_DIR)/access.o + +$(TEST_DIR)/hypercall.elf: $(cstart.o) $(TEST_DIR)/hypercall.o + +$(TEST_DIR)/sieve.elf: $(cstart.o) $(TEST_DIR)/sieve.o + +$(TEST_DIR)/vmexit.elf: $(cstart.o) $(TEST_DIR)/vmexit.o + +$(TEST_DIR)/smptest.elf: $(cstart.o) $(TEST_DIR)/smptest.o + +$(TEST_DIR)/emulator.elf: $(cstart.o) $(TEST_DIR)/emulator.o + +$(TEST_DIR)/port80.elf: $(cstart.o) $(TEST_DIR)/port80.o + +$(TEST_DIR)/tsc.elf: $(cstart.o) $(TEST_DIR)/tsc.o + +$(TEST_DIR)/tsc_adjust.elf: $(cstart.o) $(TEST_DIR)/tsc_adjust.o + +$(TEST_DIR)/apic.elf: $(cstart.o) $(TEST_DIR)/apic.o + +$(TEST_DIR)/init.elf: $(cstart.o) $(TEST_DIR)/init.o + +$(TEST_DIR)/realmode.elf: $(TEST_DIR)/realmode.o + $(CC) -m32 -nostdlib -o $@ -Wl,-T,$(TEST_DIR)/realmode.lds $^ + +$(TEST_DIR)/realmode.o: bits = 32 + +$(TEST_DIR)/msr.elf: $(cstart.o) $(TEST_DIR)/msr.o + +$(TEST_DIR)/idt_test.elf: $(cstart.o) $(TEST_DIR)/idt_test.o + +$(TEST_DIR)/xsave.elf: $(cstart.o) $(TEST_DIR)/xsave.o + +$(TEST_DIR)/rmap_chain.elf: $(cstart.o) $(TEST_DIR)/rmap_chain.o + +$(TEST_DIR)/svm.elf: $(cstart.o) + +$(TEST_DIR)/kvmclock_test.elf: $(cstart.o) $(TEST_DIR)/kvmclock.o \ + $(TEST_DIR)/kvmclock_test.o + +$(TEST_DIR)/eventinj.elf: $(cstart.o) $(TEST_DIR)/eventinj.o + +$(TEST_DIR)/s3.elf: $(cstart.o) $(TEST_DIR)/s3.o + +$(TEST_DIR)/pmu.elf: $(cstart.o) $(TEST_DIR)/pmu.o + +$(TEST_DIR)/asyncpf.elf: $(cstart.o) $(TEST_DIR)/asyncpf.o + +$(TEST_DIR)/pcid.elf: $(cstart.o) $(TEST_DIR)/pcid.o + +$(TEST_DIR)/vmx.elf: $(cstart.o) $(TEST_DIR)/vmx.o $(TEST_DIR)/vmx_tests.o + +arch_clean: + $(RM) $(TEST_DIR)/*.o $(TEST_DIR)/*.flat $(TEST_DIR)/*.elf \ + $(TEST_DIR)/.*.d $(TEST_DIR)/lib/.*.d $(TEST_DIR)/lib/*.o + +api/%.o: CFLAGS += -m32 + +api/%: LDLIBS += -lstdc++ -lboost_thread-mt -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 diff --git a/kvm-unittest/config-x86_64.mak b/kvm-unittest/config-x86_64.mak new file mode 100644 index 0000000..bb8ee89 --- /dev/null +++ b/kvm-unittest/config-x86_64.mak @@ -0,0 +1,14 @@ +TEST_DIR=x86 +cstart.o = $(TEST_DIR)/cstart64.o +bits = 64 +ldarch = elf64-x86-64 +CFLAGS += -D__x86_64__ + +tests = $(TEST_DIR)/access.flat $(TEST_DIR)/apic.flat \ + $(TEST_DIR)/emulator.flat $(TEST_DIR)/idt_test.flat \ + $(TEST_DIR)/xsave.flat $(TEST_DIR)/rmap_chain.flat \ + $(TEST_DIR)/pcid.flat +tests += $(TEST_DIR)/svm.flat +tests += $(TEST_DIR)/vmx.flat + +include config-x86-common.mak diff --git a/kvm-unittest/configure b/kvm-unittest/configure new file mode 100755 index 0000000..d0c62e2 --- /dev/null +++ b/kvm-unittest/configure @@ -0,0 +1,92 @@ +#!/bin/bash + +prefix=/usr/local +kerneldir=/lib/modules/$(uname -r)/build +cc=gcc +ld=ld +objcopy=objcopy +ar=ar +arch=`uname -m | sed -e s/i.86/i386/` +processor="$arch" +cross_prefix= + +usage() { + cat <<-EOF + Usage: $0 [options] + + Options include: + --arch=ARCH architecture to compile for ($arch) + --cross-prefix=PREFIX cross compiler prefix + --cc=CC c compiler to use ($cc) + --ld=LD ld linker to use ($ld) + --prefix=PREFIX where to install things ($prefix) + --kerneldir=DIR kernel build directory for kvm.h ($kerneldir) +EOF + exit 1 +} + +while [[ "$1" = -* ]]; do + opt="$1"; shift + arg= + if [[ "$opt" = *=* ]]; then + arg="${opt#*=}" + opt="${opt%%=*}" + fi + case "$opt" in + --prefix) + prefix="$arg" + ;; + --kerneldir) + kerneldir="$arg" + ;; + --arch) + arch="$arg" + ;; + --processor) + processor="$arg" + ;; + --cross-prefix) + cross_prefix="$arg" + ;; + --cc) + cc="$arg" + ;; + --ld) + ld="$arg" + ;; + --help) + usage + ;; + *) + usage + ;; + esac +done + +# check for dependent 32 bit libraries +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 +fi +rm -f lib_test.c + +cat < config.mak +PREFIX=$prefix +KERNELDIR=$(readlink -f $kerneldir) +ARCH=$arch +PROCESSOR=$processor +CC=$cross_prefix$cc +LD=$cross_prefix$ld +OBJCOPY=$cross_prefix$objcopy +AR=$cross_prefix$ar +API=$api +EOF diff --git a/kvm-unittest/flat.lds b/kvm-unittest/flat.lds new file mode 100644 index 0000000..a278b56 --- /dev/null +++ b/kvm-unittest/flat.lds @@ -0,0 +1,21 @@ +SECTIONS +{ + . = 4M + SIZEOF_HEADERS; + stext = .; + .text : { *(.init) *(.text) *(.text.*) } + . = ALIGN(4K); + .data : { + *(.data) + exception_table_start = .; + *(.data.ex) + exception_table_end = .; + } + . = ALIGN(16); + .rodata : { *(.rodata) } + . = ALIGN(16); + .bss : { *(.bss) } + . = ALIGN(4K); + edata = .; +} + +ENTRY(start) diff --git a/kvm-unittest/formats b/kvm-unittest/formats new file mode 100644 index 0000000..7f4ebdb --- /dev/null +++ b/kvm-unittest/formats @@ -0,0 +1,31 @@ +0x00000000 %(ts)d (+%(relts)12d) unknown (0x%(event)016x) vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ 0x%(1)08x 0x%(2)08x 0x%(3)08x 0x%(4)08x 0x%(5)08x ] + +0x00010001 %(ts)d (+%(relts)12d) VMENTRY vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x +0x00010002 %(ts)d (+%(relts)12d) VMEXIT vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ exitcode = 0x%(1)08x, rip = 0x%(3)08x %(2)08x ] +0x00020001 %(ts)d (+%(relts)12d) PAGE_FAULT vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ errorcode = 0x%(1)08x, virt = 0x%(3)08x %(2)08x ] +0x00020002 %(ts)d (+%(relts)12d) INJ_VIRQ vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ vector = 0x%(1)02x ] +0x00020003 %(ts)d (+%(relts)12d) REDELIVER_EVT vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ vector = 0x%(1)02x ] +0x00020004 %(ts)d (+%(relts)12d) PEND_INTR vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ vector = 0x%(1)02x ] +0x00020005 %(ts)d (+%(relts)12d) IO_READ vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ port = 0x%(1)04x, size = %(2)d ] +0x00020006 %(ts)d (+%(relts)12d) IO_WRITE vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ port = 0x%(1)04x, size = %(2)d ] +0x00020007 %(ts)d (+%(relts)12d) CR_READ vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ CR# = %(1)d, value = 0x%(3)08x %(2)08x ] +0x00020008 %(ts)d (+%(relts)12d) CR_WRITE vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ CR# = %(1)d, value = 0x%(3)08x %(2)08x ] +0x00020009 %(ts)d (+%(relts)12d) DR_READ vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ DR# = %(1)d, value = 0x%(2)08x ] +0x0002000A %(ts)d (+%(relts)12d) DR_WRITE vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ DR# = %(1)d, value = 0x%(2)08x ] +0x0002000B %(ts)d (+%(relts)12d) MSR_READ vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ MSR# = 0x%(1)08x, data = 0x%(3)08x %(2)08x ] +0x0002000C %(ts)d (+%(relts)12d) MSR_WRITE vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ MSR# = 0x%(1)08x, data = 0x%(3)08x %(2)08x ] +0x0002000D %(ts)d (+%(relts)12d) CPUID vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ func = 0x%(1)08x, eax = 0x%(2)08x, ebx = 0x%(3)08x, ecx = 0x%(4)08x edx = 0x%(5)08x] +0x0002000E %(ts)d (+%(relts)12d) INTR vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ vector = 0x%(1)02x ] +0x0002000F %(ts)d (+%(relts)12d) NMI vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x +0x00020010 %(ts)d (+%(relts)12d) VMMCALL vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ func = 0x%(1)08x ] +0x00020011 %(ts)d (+%(relts)12d) HLT vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x +0x00020012 %(ts)d (+%(relts)12d) CLTS vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x +0x00020013 %(ts)d (+%(relts)12d) LMSW vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ value = 0x%(1)08x ] +0x00020014 %(ts)d (+%(relts)12d) APIC_ACCESS vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ offset = 0x%(1)08x ] +0x00020015 %(ts)d (+%(relts)12d) TDP_FAULT vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ errorcode = 0x%(1)08x, virt = 0x%(3)08x %(2)08x ] +# ppc: tlb traces +0x00020016 GTLB_WRITE vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ index = 0x%(1)08x, tid = 0x%(2)08x, word1=0x%(3)08x, word2=0x%(4)08x, word3=0x%(5)08x ] +0x00020017 STLB_WRITE vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ index = 0x%(1)08x, tid = 0x%(2)08x, word1=0x%(3)08x, word2=0x%(4)08x, word3=0x%(5)08x ] +0x00020018 STLB_INVAL vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ index = 0x%(1)08x, tid = 0x%(2)08x, word1=0x%(3)08x, word2=0x%(4)08x, word3=0x%(5)08x ] +# ppc: instruction emulation - this type is handled more complex in kvmtrace_format, but listed to show the eventid and transported data +#0x00020019 %(ts)d (+%(relts)12d) PPC_INSTR vcpu = 0x%(vcpu)08x pid = 0x%(pid)08x [ instr = 0x%(1)08x, pc = 0x%(2)08x, emul = 0x%(3)08x, nsec = %(4)08d ] diff --git a/kvm-unittest/kvmtrace_format b/kvm-unittest/kvmtrace_format new file mode 100755 index 0000000..6556475 --- /dev/null +++ b/kvm-unittest/kvmtrace_format @@ -0,0 +1,532 @@ +#!/usr/bin/env python + +# by Mark Williamson, (C) 2004 Intel Research Cambridge + +# Program for reformatting trace buffer output according to user-supplied rules + +import re, sys, string, signal, struct, os, getopt, operator + +PREFIX = '/usr' +DATADIR = os.path.join(PREFIX, 'share') +KVMDIR = os.path.join(DATADIR, 'kvm') +FORMATS_FILE = os.path.join(KVMDIR, 'formats') + +def usage(): + print >> sys.stderr, \ + "Usage: " + sys.argv[0] + """ defs-file + Parses trace data in binary format, as output by kvmtrace and + reformats it according to the rules in a file of definitions. The + rules in this file should have the format ({ and } show grouping + and are not part of the syntax): + + {event_id}{whitespace}{text format string} + + The textual format string may include format specifiers, such as: + %(ts)d, %(event)d, %(pid)d %(vcpu)d %(1)d, %(2)d, + %(3)d, %(4)d, %(5)d + [ the 'd' format specifier outputs in decimal, alternatively 'x' + will output in hexadecimal and 'o' will output in octal ] + + Which correspond to the event ID, timestamp counter, pid + , vcpu and the 5 data fields from the trace record. There should be + one such rule for each type of event. + Depending on your system and the volume of trace buffer data, + this script may not be able to keep up with the output of kvmtrace + if it is piped directly. In these circumstances you should have + kvmtrace output to a file for processing off-line. + + kvmtrace_format has the following additional switches + -s - if this switch is set additional trace statistics are + created and printed at the end of the output + """ + sys.exit(1) + +def read_defs(defs_file): + defs = {} + + fd = open(defs_file) + + reg = re.compile('(\S+)\s+(\S.*)') + + while True: + line = fd.readline() + if not line: + break + + if line[0] == '#' or line[0] == '\n': + continue + + m = reg.match(line) + + if not m: print >> sys.stderr, "Bad format file" ; sys.exit(1) + + defs[str(eval(m.group(1)))] = m.group(2) + + return defs + +def sighand(x,y): + global interrupted + interrupted = 1 + +# ppc instruction decoding for event type 0x00020019 (PPC_INSTR) +# some globals for statistic summaries +stat_ppc_instr_mnemonic = {}; +stat_ppc_instr_spr = {}; +stat_ppc_instr_dcr = {}; +stat_ppc_instr_tlb = {}; + +def ppc_instr_print_summary(sortedlist, colname): + print "\n\n%14s + %10s" % (colname, "count") + print "%s" % (15*"-"+"+"+11*"-") + sum = 0 + for value, key in sortedlist: + sum += key + print "%14s | %10d" % (value, key) + print "%14s = %10d" % ("sum", sum) + + +def ppc_instr_summary(): + # don't print empty statistics + if stat_ppc_instr_mnemonic: + ppc_instr_print_summary(sorted(stat_ppc_instr_mnemonic.iteritems(), key=operator.itemgetter(1), reverse=True), "mnemonic") + if stat_ppc_instr_spr: + ppc_instr_print_summary(sorted(stat_ppc_instr_spr.iteritems(), key=operator.itemgetter(1), reverse=True), "mnemonic-spr") + if stat_ppc_instr_dcr: + ppc_instr_print_summary(sorted(stat_ppc_instr_dcr.iteritems(), key=operator.itemgetter(1), reverse=True), "mnemonic-dcr") + if stat_ppc_instr_tlb: + ppc_instr_print_summary(sorted(stat_ppc_instr_tlb.iteritems(), key=operator.itemgetter(1), reverse=True), "mnemonic-tlb") + +def get_op(instr): + return (instr >> 26); + +def get_xop(instr): + return (instr >> 1) & 0x3ff; + +def get_sprn(instr): + return ((instr >> 16) & 0x1f) | ((instr >> 6) & 0x3e0) + +def get_dcrn(instr): + return ((instr >> 16) & 0x1f) | ((instr >> 6) & 0x3e0); + +def get_tlbwe_type(instr): + ws = (instr >> 11) & 0x1f; + if ws == 0: + return "PAGEID" + elif ws == 1: + return "XLAT" + elif ws == 2: + return "ATTRIB" + else: + return "UNKNOWN" + +def get_name(instr): + if get_op(instr)==3: + return "trap" + elif get_op(instr)==19: + if get_xop(instr) == 50: + return "rfi" + else: + return "unknown" + elif get_op(instr)==31: + if get_xop(instr) == 83: + return "mfmsr" + + elif get_xop(instr) == 87: + return "lbzx" + + elif get_xop(instr) == 131: + return "wrtee" + + elif get_xop(instr) == 146: + return "mtmsr" + + elif get_xop(instr) == 163: + return "wrteei" + + elif get_xop(instr) == 215: + return "stbx" + + elif get_xop(instr) == 247: + return "stbux" + + elif get_xop(instr) == 279: + return "lhzx" + + elif get_xop(instr) == 311: + return "lhzux" + + elif get_xop(instr) == 323: + return "mfdcr" + + elif get_xop(instr) == 339: + return "mfspr" + + elif get_xop(instr) == 407: + return "sthx" + + elif get_xop(instr) == 439: + return "sthux" + + elif get_xop(instr) == 451: + return "mtdcr" + + elif get_xop(instr) == 467: + return "mtspr" + + elif get_xop(instr) == 470: + return "dcbi" + + elif get_xop(instr) == 534: + return "lwbrx" + + elif get_xop(instr) == 566: + return "tlbsync" + + elif get_xop(instr) == 662: + return "stwbrx" + + elif get_xop(instr) == 978: + return "tlbwe" + + elif get_xop(instr) == 914: + return "tlbsx" + + elif get_xop(instr) == 790: + return "lhbrx" + + elif get_xop(instr) == 918: + return "sthbrx" + + elif get_xop(instr) == 966: + return "iccci" + + else: + return "unknown" + + elif get_op(instr) == 32: + return "lwz" + + elif get_op(instr) == 33: + return "lwzu" + + elif get_op(instr) == 34: + return "lbz" + + elif get_op(instr) == 35: + return "lbzu" + + elif get_op(instr) == 36: + return "stw" + + elif get_op(instr) == 37: + return "stwu" + + elif get_op(instr) == 38: + return "stb" + + elif get_op(instr) == 39: + return "stbu" + + elif get_op(instr) == 40: + return "lhz" + + elif get_op(instr) == 41: + return "lhzu" + + elif get_op(instr) == 44: + return "sth" + + elif get_op(instr) == 45: + return "sthu" + + else: + return "unknown" + +def get_sprn_name(sprn): + if sprn == 0x01a: + return "SRR0" + elif sprn == 0x01b: + return "SRR1" + elif sprn == 0x3b2: + return "MMUCR" + elif sprn == 0x030: + return "PID" + elif sprn == 0x03f: + return "IVPR" + elif sprn == 0x3b3: + return "CCR0" + elif sprn == 0x378: + return "CCR1" + elif sprn == 0x11f: + return "PVR" + elif sprn == 0x03d: + return "DEAR" + elif sprn == 0x03e: + return "ESR" + elif sprn == 0x134: + return "DBCR0" + elif sprn == 0x135: + return "DBCR1" + elif sprn == 0x11c: + return "TBWL" + elif sprn == 0x11d: + return "TBWU" + elif sprn == 0x016: + return "DEC" + elif sprn == 0x150: + return "TSR" + elif sprn == 0x154: + return "TCR" + elif sprn == 0x110: + return "SPRG0" + elif sprn == 0x111: + return "SPRG1" + elif sprn == 0x112: + return "SPRG2" + elif sprn == 0x113: + return "SPRG3" + elif sprn == 0x114: + return "SPRG4" + elif sprn == 0x115: + return "SPRG5" + elif sprn == 0x116: + return "SPRG6" + elif sprn == 0x117: + return "SPRG7" + elif sprn == 0x190: + return "IVOR0" + elif sprn == 0x191: + return "IVOR1" + elif sprn == 0x192: + return "IVOR2" + elif sprn == 0x193: + return "IVOR3" + elif sprn == 0x194: + return "IVOR4" + elif sprn == 0x195: + return "IVOR5" + elif sprn == 0x196: + return "IVOR6" + elif sprn == 0x197: + return "IVOR7" + elif sprn == 0x198: + return "IVOR8" + elif sprn == 0x199: + return "IVOR9" + elif sprn == 0x19a: + return "IVOR10" + elif sprn == 0x19b: + return "IVOR11" + elif sprn == 0x19c: + return "IVOR12" + elif sprn == 0x19d: + return "IVOR13" + elif sprn == 0x19e: + return "IVOR14" + elif sprn == 0x19f: + return "IVOR15" + else: + return "UNKNOWN" + +def get_special(instr): + name = get_name(instr); + if stat_ppc_instr_mnemonic.has_key(name): + stat_ppc_instr_mnemonic[name] += 1 + else: + stat_ppc_instr_mnemonic[name] = 1 + + if get_op(instr) == 31: + if (get_xop(instr) == 339) or (get_xop(instr) == 467): + sprn = get_sprn(instr); + sprn_name = get_sprn_name(sprn); + stat_idx = name+"-"+sprn_name + if stat_ppc_instr_spr.has_key(stat_idx): + stat_ppc_instr_spr[stat_idx] += 1 + else: + stat_ppc_instr_spr[stat_idx] = 1 + return ("- sprn 0x%03x %8s" % (sprn, sprn_name)) + elif (get_xop(instr) == 323 ) or (get_xop(instr) == 451): + dcrn = get_dcrn(instr); + stat_idx = name+"-"+("%04X"%dcrn) + if stat_ppc_instr_dcr.has_key(stat_idx): + stat_ppc_instr_dcr[stat_idx] += 1 + else: + stat_ppc_instr_dcr[stat_idx] = 1 + return ("- dcrn 0x%03x" % dcrn) + elif (get_xop(instr) == 978 ) or (get_xop(instr) == 451): + tlbwe_type = get_tlbwe_type(instr) + stat_idx = name+"-"+tlbwe_type + if stat_ppc_instr_tlb.has_key(stat_idx): + stat_ppc_instr_tlb[stat_idx] += 1 + else: + stat_ppc_instr_tlb[stat_idx] = 1 + return ("- ws -> %8s" % tlbwe_type) + return "" + +##### Main code + +summary = False + +try: + opts, arg = getopt.getopt(sys.argv[1:], "sc:" ) + for opt in opts: + if opt[0] == '-s' : summary = True + +except getopt.GetoptError: + usage() + +signal.signal(signal.SIGTERM, sighand) +signal.signal(signal.SIGHUP, sighand) +signal.signal(signal.SIGINT, sighand) + +interrupted = 0 + +if len(arg) > 0: + defs = read_defs(arg[0]) +else: + defs = read_defs(FORMATS_FILE) + +# structure of trace record (as output by kvmtrace): +# HDR(I) {TSC(Q)} D1(I) D2(I) D3(I) D4(I) D5(I) +# +# HDR consists of EVENT:28:, n_data:3:, ts_in:1: +# pid:32, vcpu_id:32 +# EVENT means Event ID +# n_data means number of data (like D1, D2, ...) +# ts_in means Timestamp data exists(1) or not(0). +# if ts_in == 0, TSC(Q) does not exists. +# +HDRREC = "> sys.stderr, "Bad data file: magic number error." + break; + else: + HDRREC = ">III" + TSCREC = ">Q" + D1REC = ">I" + D2REC = ">II" + D3REC = ">III" + D4REC = ">IIII" + D5REC = ">IIIII" + continue + + line = sys.stdin.read(struct.calcsize(HDRREC)) + if not line: + break + (event, pid, vcpu_id) = struct.unpack(HDRREC, line) + + n_data = event >> 28 & 0x7 + ts_in = event >> 31 + + d1 = 0 + d2 = 0 + d3 = 0 + d4 = 0 + d5 = 0 + + ts = 0 + + if ts_in == 1: + line = sys.stdin.read(struct.calcsize(TSCREC)) + if not line: + break + ts = struct.unpack(TSCREC, line)[0] + if n_data == 1: + line = sys.stdin.read(struct.calcsize(D1REC)) + if not line: + break + d1 = struct.unpack(D1REC, line)[0] + if n_data == 2: + line = sys.stdin.read(struct.calcsize(D2REC)) + if not line: + break + (d1, d2) = struct.unpack(D2REC, line) + if n_data == 3: + line = sys.stdin.read(struct.calcsize(D3REC)) + if not line: + break + (d1, d2, d3) = struct.unpack(D3REC, line) + if n_data == 4: + line = sys.stdin.read(struct.calcsize(D4REC)) + if not line: + break + (d1, d2, d3, d4) = struct.unpack(D4REC, line) + if n_data == 5: + line = sys.stdin.read(struct.calcsize(D5REC)) + if not line: + break + (d1, d2, d3, d4, d5) = struct.unpack(D5REC, line) + + event &= 0x0fffffff + + # provide relative TSC + + if last_ts > 0 and ts_in == 1: + relts = ts - last_ts + else: + relts = 0 + + if ts_in == 1: + last_ts = ts + + args = {'ts' : ts, + 'event' : event, + 'relts': relts, + 'pid' : pid, + 'vcpu' : vcpu_id, + '1' : d1, + '2' : d2, + '3' : d3, + '4' : d4, + '5' : d5 } + + # some event types need more than just formats mapping they are if/elif + # chained here and the last default else is the mapping via formats + if event == 0x00020019: + pdata = (ts, relts, vcpu_id, pid, d1, d2, d3, get_name(d1), get_special(d1)) + print "%d (+%12d) PPC_INSTR vcpu = 0x%08x pid = 0x%08x [ instr = 0x%08x, pc = 0x%08x, emul = %01d, mnemonic = %8s %s" % pdata + else: + try: + if defs.has_key(str(event)): + print defs[str(event)] % args + else: + if defs.has_key(str(0)): print defs[str(0)] % args + except TypeError: + if defs.has_key(str(event)): + print defs[str(event)] + print args + else: + if defs.has_key(str(0)): + print defs[str(0)] + print args + + except IOError, struct.error: sys.exit() + +if summary: + ppc_instr_summary() diff --git a/kvm-unittest/lib/powerpc/44x/timebase.S b/kvm-unittest/lib/powerpc/44x/timebase.S new file mode 100644 index 0000000..385904d --- /dev/null +++ b/kvm-unittest/lib/powerpc/44x/timebase.S @@ -0,0 +1,28 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +/* unsigned long long mftb(void); */ +.global mftb +mftb: + mftbu r5 + mftbl r4 + mftbu r3 + cmpw r3, r5 + bne mftb + blr diff --git a/kvm-unittest/lib/powerpc/44x/tlbwe.S b/kvm-unittest/lib/powerpc/44x/tlbwe.S new file mode 100644 index 0000000..3790374 --- /dev/null +++ b/kvm-unittest/lib/powerpc/44x/tlbwe.S @@ -0,0 +1,29 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +#define SPRN_MMUCR 0x3b2 + +/* tlbwe(uint index, uint8_t tid, uint word0, uint word1, uint word2) */ +.global tlbwe +tlbwe: + mtspr SPRN_MMUCR, r4 + tlbwe r5, r3, 0 + tlbwe r6, r3, 1 + tlbwe r7, r3, 2 + blr diff --git a/kvm-unittest/powerpc/44x/tlbsx.S b/kvm-unittest/powerpc/44x/tlbsx.S new file mode 100644 index 0000000..b15874b --- /dev/null +++ b/kvm-unittest/powerpc/44x/tlbsx.S @@ -0,0 +1,33 @@ +#define SPRN_MMUCR 0x3b2 + +#define TLBWORD0 0x10000210 +#define TLBWORD1 0x10000000 +#define TLBWORD2 0x00000003 + +.global _start +_start: + li r4, 0 + mtspr SPRN_MMUCR, r4 + + li r3, 23 + + lis r4, TLBWORD0@h + ori r4, r4, TLBWORD0@l + tlbwe r4, r3, 0 + + lis r4, TLBWORD1@h + ori r4, r4, TLBWORD1@l + tlbwe r4, r3, 1 + + lis r4, TLBWORD2@h + ori r4, r4, TLBWORD2@l + tlbwe r4, r3, 2 + + lis r4, 0x1000 + tlbsx r5, r4, r0 + cmpwi r5, 23 + beq good + trap + +good: + b . diff --git a/kvm-unittest/powerpc/44x/tlbwe.S b/kvm-unittest/powerpc/44x/tlbwe.S new file mode 100644 index 0000000..ec6ef5c --- /dev/null +++ b/kvm-unittest/powerpc/44x/tlbwe.S @@ -0,0 +1,27 @@ +#define SPRN_MMUCR 0x3b2 + +/* Create a mapping at 4MB */ +#define TLBWORD0 0x00400210 +#define TLBWORD1 0x00400000 +#define TLBWORD2 0x00000003 + +.global _start +_start: + li r4, 0 + mtspr SPRN_MMUCR, r4 + + li r3, 23 + + lis r4, TLBWORD0@h + ori r4, r4, TLBWORD0@l + tlbwe r4, r3, 0 + + lis r4, TLBWORD1@h + ori r4, r4, TLBWORD1@l + tlbwe r4, r3, 1 + + lis r4, TLBWORD2@h + ori r4, r4, TLBWORD2@l + tlbwe r4, r3, 2 + + b . diff --git a/kvm-unittest/powerpc/44x/tlbwe_16KB.S b/kvm-unittest/powerpc/44x/tlbwe_16KB.S new file mode 100644 index 0000000..1bd10bf --- /dev/null +++ b/kvm-unittest/powerpc/44x/tlbwe_16KB.S @@ -0,0 +1,35 @@ +#define SPRN_MMUCR 0x3b2 + +/* 16KB mapping at 4MB */ +#define TLBWORD0 0x00400220 +#define TLBWORD1 0x00400000 +#define TLBWORD2 0x00000003 + +.global _start +_start: + li r4, 0 + mtspr SPRN_MMUCR, r4 + + li r3, 5 + + lis r4, TLBWORD0@h + ori r4, r4, TLBWORD0@l + tlbwe r4, r3, 0 + + lis r4, TLBWORD1@h + ori r4, r4, TLBWORD1@l + tlbwe r4, r3, 1 + + lis r4, TLBWORD2@h + ori r4, r4, TLBWORD2@l + tlbwe r4, r3, 2 + + /* load from 4MB */ + lis r3, 0x0040 + lwz r4, 0(r3) + + /* load from 4MB+8KB */ + ori r3, r3, 0x2000 + lwz r4, 0(r3) + + b . diff --git a/kvm-unittest/powerpc/44x/tlbwe_hole.S b/kvm-unittest/powerpc/44x/tlbwe_hole.S new file mode 100644 index 0000000..5efd303 --- /dev/null +++ b/kvm-unittest/powerpc/44x/tlbwe_hole.S @@ -0,0 +1,27 @@ +#define SPRN_MMUCR 0x3b2 + +/* Try to map real address 1GB. */ +#define TLBWORD0 0x40000210 +#define TLBWORD1 0x40000000 +#define TLBWORD2 0x00000003 + +.global _start +_start: + li r4, 0 + mtspr SPRN_MMUCR, r4 + + li r3, 23 + + lis r4, TLBWORD0@h + ori r4, r4, TLBWORD0@l + tlbwe r4, r3, 0 + + lis r4, TLBWORD1@h + ori r4, r4, TLBWORD1@l + tlbwe r4, r3, 1 + + lis r4, TLBWORD2@h + ori r4, r4, TLBWORD2@l + tlbwe r4, r3, 2 + + b . diff --git a/kvm-unittest/powerpc/cstart.S b/kvm-unittest/powerpc/cstart.S new file mode 100644 index 0000000..70a0e9f --- /dev/null +++ b/kvm-unittest/powerpc/cstart.S @@ -0,0 +1,38 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +#define OUTPUT_VADDR 0xf0000000 +#define OUTPUT_PADDR 0xf0000000 + +.globl _start +_start: + /* In the future we might need to assign a stack and zero BSS here. */ + + /* Map the debug page 1:1. */ + lis r3, OUTPUT_VADDR@h + ori r3, r3, OUTPUT_VADDR@l + lis r4, OUTPUT_PADDR@h + ori r4, r4, OUTPUT_PADDR@l + bl map + + /* Call main() and pass return code to exit(). */ + bl main + bl exit + + b . diff --git a/kvm-unittest/powerpc/io.S b/kvm-unittest/powerpc/io.S new file mode 100644 index 0000000..97567cb --- /dev/null +++ b/kvm-unittest/powerpc/io.S @@ -0,0 +1,32 @@ +#define SPRN_MMUCR 0x3b2 + +#define TLBWORD0 0xf0000210 +#define TLBWORD1 0xf0000000 +#define TLBWORD2 0x00000003 + +.global _start +_start: + li r4, 0 + mtspr SPRN_MMUCR, r4 + + li r3, 2 + + lis r4, TLBWORD0@h + ori r4, r4, TLBWORD0@l + tlbwe r4, r3, 0 + + lis r4, TLBWORD1@h + ori r4, r4, TLBWORD1@l + tlbwe r4, r3, 1 + + lis r4, TLBWORD2@h + ori r4, r4, TLBWORD2@l + tlbwe r4, r3, 2 + + lis r3, 0xf000 + lis r4, 0x1234 + ori r4, r4, 0x5678 + stb r4, 0(r3) + lbz r5, 0(r3) + + b . diff --git a/kvm-unittest/powerpc/spin.S b/kvm-unittest/powerpc/spin.S new file mode 100644 index 0000000..4406641 --- /dev/null +++ b/kvm-unittest/powerpc/spin.S @@ -0,0 +1,4 @@ + +.global _start +_start: + b . diff --git a/kvm-unittest/powerpc/sprg.S b/kvm-unittest/powerpc/sprg.S new file mode 100644 index 0000000..d0414a4 --- /dev/null +++ b/kvm-unittest/powerpc/sprg.S @@ -0,0 +1,7 @@ + +.global _start +_start: + li r3, 42 + mtsprg 0, r3 + mfsprg r4, 0 + b . diff --git a/kvm-unittest/run_tests.sh b/kvm-unittest/run_tests.sh new file mode 100755 index 0000000..55ecac5 --- /dev/null +++ b/kvm-unittest/run_tests.sh @@ -0,0 +1,123 @@ +#!/bin/bash + +testroot=x86 +config=$testroot/unittests.cfg +qemu=${qemu:-qemu-system-x86_64} +verbose=0 + +function run() +{ + local testname="$1" + local groups="$2" + local smp="$3" + local kernel="$4" + local opts="$5" + local arch="$6" + + if [ -z "$testname" ]; then + return + fi + + if [ -n "$only_group" ] && ! grep -q "$only_group" <<<$groups; then + return + fi + + if [ -n "$arch" ] && [ "$arch" != "$ARCH" ]; then + echo "skip $1 ($arch only)" + return + fi + + cmdline="./x86-run $kernel -smp $smp -display none $opts" + if [ $verbose != 0 ]; then + echo $cmdline + fi + + # extra_params in the config file may contain backticks that need to be + # expanded, so use eval to start qemu + eval $cmdline >> test.log + + if [ $? -le 1 ]; then + echo -e "\e[32mPASS\e[0m $1" + else + echo -e "\e[31mFAIL\e[0m $1" + fi +} + +function run_all() +{ + local config="$1" + local testname + local smp + local kernel + local opts + local groups + local arch + + exec {config_fd}<$config + + while read -u $config_fd line; do + if [[ "$line" =~ ^\[(.*)\]$ ]]; then + run "$testname" "$groups" "$smp" "$kernel" "$opts" "$arch" + testname=${BASH_REMATCH[1]} + smp=1 + kernel="" + opts="" + groups="" + arch="" + elif [[ $line =~ ^file\ *=\ *(.*)$ ]]; then + kernel=$testroot/${BASH_REMATCH[1]} + elif [[ $line =~ ^smp\ *=\ *(.*)$ ]]; then + smp=${BASH_REMATCH[1]} + elif [[ $line =~ ^extra_params\ *=\ *(.*)$ ]]; then + opts=${BASH_REMATCH[1]} + elif [[ $line =~ ^groups\ *=\ *(.*)$ ]]; then + groups=${BASH_REMATCH[1]} + elif [[ $line =~ ^arch\ *=\ *(.*)$ ]]; then + arch=${BASH_REMATCH[1]} + fi + done + + run "$testname" "$groups" "$smp" "$kernel" "$opts" "$arch" + + exec {config_fd}<&- +} + +function usage() +{ +cat < test.log +while getopts "g:hv" opt; do + case $opt in + g) + only_group=$OPTARG + ;; + h) + usage + exit + ;; + v) + verbose=1 + ;; + *) + exit + ;; + esac +done + +run_all $config diff --git a/kvm-unittest/testdev.txt b/kvm-unittest/testdev.txt new file mode 100644 index 0000000..ac436ef --- /dev/null +++ b/kvm-unittest/testdev.txt @@ -0,0 +1,14 @@ +This file describes the virtual device of qemu for supporting this test suite. + +Services supplied by the testdev device: + +serial output: write only, on io port 0xf1 +exit process: write only, on io port 0xf4, value used as exit code +ram size: read-only, on io port 0xd1, 4 bytes' size +irq line setting: write only, on io ports 0x2000 - 0x2018, value to set/clear +simple io: read/write, on io port 0xe0, 1/2/4 bytes + +Test device used a char device for actual output + + + diff --git a/kvm-unittest/x86-run b/kvm-unittest/x86-run new file mode 100755 index 0000000..646c577 --- /dev/null +++ b/kvm-unittest/x86-run @@ -0,0 +1,41 @@ +#!/bin/bash + +qemukvm="${QEMU:-qemu-kvm}" +qemusystem="${QEMU:-qemu-system-x86_64}" +if + ${qemukvm} -device '?' 2>&1 | grep -F -e \"testdev\" -e \"pc-testdev\" > /dev/null; +then + qemu="${qemukvm}" +else + if + ${qemusystem} -device '?' 2>&1 | grep -F -e \"testdev\" -e \"pc-testdev\" > /dev/null; + then + qemu="${qemusystem}" + else + echo QEMU binary ${QEMU} has no support for test device. Exiting. + exit 2 + fi +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} -enable-kvm $pc_testdev -display none -serial stdio $pci_testdev -kernel" +echo ${command} "$@" +${command} "$@" +ret=$? +echo Return value from qemu: $ret +exit $ret diff --git a/kvm-unittest/x86/README b/kvm-unittest/x86/README new file mode 100644 index 0000000..d644abd --- /dev/null +++ b/kvm-unittest/x86/README @@ -0,0 +1,16 @@ +Tests in this directory and what they do: + +access: lots of page table related access (pte/pde) (read/write) +apic: enable x2apic, self ipi, ioapic intr, ioapic simultaneous +emulator: move to/from regs, cmps, push, pop, to/from cr8, smsw and lmsw +hypercall: intel and amd hypercall insn +msr: write to msr (only KERNEL_GS_BASE for now) +port80: lots of out to port 80 +realmode: goes back to realmode, shld, push/pop, mov immediate, cmp immediate, add immediate, + io, eflags instructions (clc, cli, etc.), jcc short, jcc near, call, long jmp, xchg +sieve: heavy memory access with no paging and with paging static and with paging vmalloc'ed +smptest: run smp_id() on every cpu and compares return value to number +tsc: write to tsc(0) and write to tsc(100000000000) and read it back +vmexit: long loops for each: cpuid, vmcall, mov_from_cr8, mov_to_cr8, inl_pmtimer, ipi, ipi+halt +kvmclock_test: test of wallclock, monotonic cycle and performance of kvmclock +pcid: basic functionality test of PCID/INVPCID feature \ No newline at end of file diff --git a/kvm-unittest/x86/cstart.S b/kvm-unittest/x86/cstart.S new file mode 100644 index 0000000..bc8d563 --- /dev/null +++ b/kvm-unittest/x86/cstart.S @@ -0,0 +1,192 @@ + +#include "apic-defs.h" + +.globl boot_idt +boot_idt = 0 + +ipi_vector = 0x20 + +max_cpus = 64 + +.bss + + . = . + 4096 * max_cpus + .align 16 +stacktop: + + . = . + 4096 + .align 16 +ring0stacktop: + +.data + +.align 4096 +pt: +i = 0 + .rept 1024 + .long 0x1e7 | (i << 22) + i = i + 1 + .endr + +gdt32: + .quad 0 + .quad 0x00cf9b000000ffff // flat 32-bit code segment + .quad 0x00cf93000000ffff // flat 32-bit data segment + +tss_descr: + .rept max_cpus + .quad 0x000089000000ffff // 32-bit avail tss + .endr +gdt32_end: + +i = 0 +tss: + .rept max_cpus + .long 0 + .long ring0stacktop - i * 4096 + .long 16 + .quad 0, 0 + .quad 0, 0, 0, 0, 0, 0, 0, 0 + .long 0, 0, 0 + i = i + 1 + .endr +tss_end: + +idt_descr: + .word 16 * 256 - 1 + .long boot_idt + +.section .init + +.code32 + +mb_magic = 0x1BADB002 +mb_flags = 0x0 + + # multiboot header + .long mb_magic, mb_flags, 0 - (mb_magic + mb_flags) +mb_cmdline = 16 + +MSR_GS_BASE = 0xc0000101 + +.macro setup_percpu_area + lea -4096(%esp), %eax + mov $0, %edx + mov $MSR_GS_BASE, %ecx + wrmsr +.endm + +.globl start +start: + mov mb_cmdline(%ebx), %eax + mov %eax, __args + call __setup_args + mov $stacktop, %esp + setup_percpu_area + call prepare_32 + jmpl $8, $start32 + +prepare_32: + lgdtl gdt32_descr + + mov %cr4, %eax + bts $4, %eax // pse + mov %eax, %cr4 + + mov $pt, %eax + mov %eax, %cr3 + + mov %cr0, %eax + bts $0, %eax + bts $31, %eax + mov %eax, %cr0 + ret + +smp_stacktop: .long 0xa0000 + +ap_start32: + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + mov $-4096, %esp + lock/xaddl %esp, smp_stacktop + setup_percpu_area + call prepare_32 + call load_tss + call enable_apic + call enable_x2apic + sti + nop + lock incw cpu_online_count + +1: hlt + jmp 1b + +start32: + call load_tss + call mask_pic_interrupts + call enable_apic + call smp_init + call enable_x2apic + push $__argv + push __argc + call main + push %eax + call exit + +load_tss: + lidt idt_descr + mov $16, %eax + mov %ax, %ss + mov $(APIC_DEFAULT_PHYS_BASE + APIC_ID), %eax + mov (%eax), %eax + shr $24, %eax + mov %eax, %ebx + shl $3, %ebx + mov $((tss_end - tss) / max_cpus), %edx + imul %edx + add $tss, %eax + mov %ax, tss_descr+2(%ebx) + shr $16, %eax + mov %al, tss_descr+4(%ebx) + shr $8, %eax + mov %al, tss_descr+7(%ebx) + lea tss_descr-gdt32(%ebx), %eax + ltr %ax + ret + +smp_init: + cld + lea sipi_entry, %esi + xor %edi, %edi + mov $(sipi_end - sipi_entry), %ecx + rep/movsb + mov $APIC_DEFAULT_PHYS_BASE, %eax + movl $(APIC_DEST_ALLBUT | APIC_DEST_PHYSICAL | APIC_DM_INIT | APIC_INT_ASSERT), APIC_ICR(%eax) + movl $(APIC_DEST_ALLBUT | APIC_DEST_PHYSICAL | APIC_DM_INIT), APIC_ICR(%eax) + movl $(APIC_DEST_ALLBUT | APIC_DEST_PHYSICAL | APIC_DM_STARTUP), APIC_ICR(%eax) + call fwcfg_get_nb_cpus +1: pause + cmpw %ax, cpu_online_count + jne 1b +smp_init_done: + ret + +cpu_online_count: .word 1 + +.code16 +sipi_entry: + mov %cr0, %eax + or $1, %eax + mov %eax, %cr0 + lgdtl gdt32_descr - sipi_entry + ljmpl $8, $ap_start32 + +gdt32_descr: + .word gdt32_end - gdt32 - 1 + .long gdt32 + +sipi_end: diff --git a/kvm-unittest/x86/cstart64.S b/kvm-unittest/x86/cstart64.S new file mode 100644 index 0000000..0fe76da --- /dev/null +++ b/kvm-unittest/x86/cstart64.S @@ -0,0 +1,245 @@ + +#include "apic-defs.h" + +.globl boot_idt +boot_idt = 0 + +.globl idt_descr +.globl tss_descr +.globl gdt64_desc + +ipi_vector = 0x20 + +max_cpus = 64 + +.bss + + . = . + 4096 * max_cpus + .align 16 +stacktop: + + . = . + 4096 + .align 16 +ring0stacktop: + +.data + +.align 4096 +.globl ptl2 +ptl2: +i = 0 + .rept 512 * 4 + .quad 0x1e7 | (i << 21) + i = i + 1 + .endr + +.align 4096 +ptl3: + .quad ptl2 + 7 + 0 * 4096 + .quad ptl2 + 7 + 1 * 4096 + .quad ptl2 + 7 + 2 * 4096 + .quad ptl2 + 7 + 3 * 4096 + +.align 4096 +ptl4: + .quad ptl3 + 7 + +.align 4096 + +gdt64_desc: + .word gdt64_end - gdt64 - 1 + .quad gdt64 + +gdt64: + .quad 0 + .quad 0x00af9b000000ffff // 64-bit code segment + .quad 0x00cf93000000ffff // 64-bit data segment + .quad 0x00affb000000ffff // 64-bit code segment (user) + .quad 0x00cff3000000ffff // 64-bit data segment (user) + .quad 0x00cf9b000000ffff // 32-bit code segment + .quad 0x00cf92000000ffff // 32-bit code segment + .quad 0x008F9A000000FFFF // 16-bit code segment + .quad 0x008F92000000FFFF // 16-bit data segment + +tss_descr: + .rept max_cpus + .quad 0x000089000000ffff // 64-bit avail tss + .quad 0 // tss high addr + .endr +gdt64_end: + +i = 0 +tss: + .rept max_cpus + .long 0 + .quad ring0stacktop - i * 4096 + .quad 0, 0 + .quad 0, 0, 0, 0, 0, 0, 0, 0 + .long 0, 0, 0 +i = i + 1 + .endr +tss_end: + +mb_boot_info: .quad 0 + +.section .init + +.code32 + +mb_magic = 0x1BADB002 +mb_flags = 0x0 + + # multiboot header + .long mb_magic, mb_flags, 0 - (mb_magic + mb_flags) +mb_cmdline = 16 + +MSR_GS_BASE = 0xc0000101 + +.macro setup_percpu_area + lea -4096(%esp), %eax + mov $0, %edx + mov $MSR_GS_BASE, %ecx + wrmsr +.endm + +.globl start +start: + mov %ebx, mb_boot_info + mov $stacktop, %esp + setup_percpu_area + call prepare_64 + jmpl $8, $start64 + +prepare_64: + lgdt gdt64_desc + + mov %cr4, %eax + bts $5, %eax // pae + mov %eax, %cr4 + + mov $ptl4, %eax + mov %eax, %cr3 + +efer = 0xc0000080 + mov $efer, %ecx + rdmsr + bts $8, %eax + wrmsr + + mov %cr0, %eax + bts $0, %eax + bts $31, %eax + mov %eax, %cr0 + ret + +smp_stacktop: .long 0xa0000 + +.align 16 + +gdt32: + .quad 0 + .quad 0x00cf9b000000ffff // flat 32-bit code segment + .quad 0x00cf93000000ffff // flat 32-bit data segment +gdt32_end: + +.code16 +sipi_entry: + mov %cr0, %eax + or $1, %eax + mov %eax, %cr0 + lgdtl gdt32_descr - sipi_entry + ljmpl $8, $ap_start32 + +gdt32_descr: + .word gdt32_end - gdt32 - 1 + .long gdt32 + +sipi_end: + +.code32 +ap_start32: + mov $0x10, %ax + mov %ax, %ds + mov %ax, %es + mov %ax, %fs + mov %ax, %gs + mov %ax, %ss + mov $-4096, %esp + lock/xaddl %esp, smp_stacktop + setup_percpu_area + call prepare_64 + ljmpl $8, $ap_start64 + +.code64 +ap_start64: + call load_tss + call enable_apic + call enable_x2apic + sti + nop + lock incw cpu_online_count + +1: hlt + jmp 1b + +start64: + call load_tss + call mask_pic_interrupts + call enable_apic + call smp_init + call enable_x2apic + mov mb_boot_info(%rip), %rax + mov mb_cmdline(%rax), %rax + mov %rax, __args(%rip) + call __setup_args + mov __argc(%rip), %edi + lea __argv(%rip), %rsi + call main + mov %eax, %edi + call exit + +idt_descr: + .word 16 * 256 - 1 + .quad boot_idt + +load_tss: + lidtq idt_descr + mov $0, %eax + mov %ax, %ss + mov $(APIC_DEFAULT_PHYS_BASE + APIC_ID), %eax + mov (%rax), %eax + shr $24, %eax + mov %eax, %ebx + shl $4, %ebx + mov $((tss_end - tss) / max_cpus), %edx + imul %edx + add $tss, %rax + mov %ax, tss_descr+2(%rbx) + shr $16, %rax + mov %al, tss_descr+4(%rbx) + shr $8, %rax + mov %al, tss_descr+7(%rbx) + shr $8, %rax + mov %eax, tss_descr+8(%rbx) + lea tss_descr-gdt64(%rbx), %rax + ltr %ax + ret + +smp_init: + cld + lea sipi_entry, %rsi + xor %rdi, %rdi + mov $(sipi_end - sipi_entry), %rcx + rep/movsb + mov $APIC_DEFAULT_PHYS_BASE, %eax + movl $(APIC_DEST_ALLBUT | APIC_DEST_PHYSICAL | APIC_DM_INIT | APIC_INT_ASSERT), APIC_ICR(%rax) + movl $(APIC_DEST_ALLBUT | APIC_DEST_PHYSICAL | APIC_DM_INIT), APIC_ICR(%rax) + movl $(APIC_DEST_ALLBUT | APIC_DEST_PHYSICAL | APIC_DM_STARTUP), APIC_ICR(%rax) + call fwcfg_get_nb_cpus +1: pause + cmpw %ax, cpu_online_count + jne 1b +smp_init_done: + ret + +cpu_online_count: .word 1 diff --git a/kvm-unittest/x86/realmode.lds b/kvm-unittest/x86/realmode.lds new file mode 100644 index 0000000..0ed3063 --- /dev/null +++ b/kvm-unittest/x86/realmode.lds @@ -0,0 +1,12 @@ +SECTIONS +{ + . = 16K; + stext = .; + .text : { *(.init) *(.text) } + . = ALIGN(4K); + .data : { *(.data) *(.rodata*) } + . = ALIGN(16); + .bss : { *(.bss) } + edata = .; +} +ENTRY(start) diff --git a/kvm-unittest/x86/run-kvm-unit-tests b/kvm-unittest/x86/run-kvm-unit-tests new file mode 100644 index 0000000..fed925a --- /dev/null +++ b/kvm-unittest/x86/run-kvm-unit-tests @@ -0,0 +1,6 @@ +#!/usr/bin/python + +import sys, os, os.path + +prog = sys.argv[0] +dir = os.path.dirname(prog) diff --git a/kvm-unittest/x86/unittests.cfg b/kvm-unittest/x86/unittests.cfg new file mode 100644 index 0000000..85c36aa --- /dev/null +++ b/kvm-unittest/x86/unittests.cfg @@ -0,0 +1,157 @@ +# Define your new unittest following the convention: +# [unittest_name] +# file = foo.flat # Name of the flat file to be used +# smp = 2 # Number of processors the VM will use during this test +# extra_params = -cpu qemu64,+x2apic # Additional parameters used +# arch = i386/x86_64 # Only if the test case works only on one of them +# groups = group1 group2 # Used to identify test cases with run_tests -g ... + +[apic] +file = apic.flat +smp = 2 +extra_params = -cpu qemu64,+x2apic +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 + +#[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 + +[port80] +file = port80.flat + +[realmode] +file = realmode.flat + +[s3] +file = s3.flat + +[sieve] +file = sieve.flat + +[tsc] +file = tsc.flat + +[tsc_adjust] +file = tsc_adjust.flat + +[xsave] +file = xsave.flat +arch = x86_64 + +[rmap_chain] +file = rmap_chain.flat +arch = x86_64 + +[svm] +file = svm.flat +smp = 2 +extra_params = -cpu qemu64,+svm +arch = x86_64 + +[svm-disabled] +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 +