[COMMIT master] Add another task switch test

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Gleb Natapov <gleb@xxxxxxxxxx>

It has more test cases then existing one.  These test cases were written
when I worked on fixing task switch emulation code. Most of them check
for previously existing issue.

Signed-off-by: Gleb Natapov <gleb@xxxxxxxxxx>
Signed-off-by: Avi Kivity <avi@xxxxxxxxxx>

diff --git a/config-i386.mak b/config-i386.mak
index c1b6e08..de52f3d 100644
--- a/config-i386.mak
+++ b/config-i386.mak
@@ -5,8 +5,9 @@ ldarch = elf32-i386
 CFLAGS += -D__i386__
 CFLAGS += -I $(KERNELDIR)/include
 
-tests = $(TEST_DIR)/taskswitch.flat
+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/lib/x86/desc.c b/lib/x86/desc.c
index 25a25bc..2f73a5a 100644
--- a/lib/x86/desc.c
+++ b/lib/x86/desc.c
@@ -18,6 +18,50 @@ typedef struct {
 #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;
+
+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;
+
 static idt_entry_t idt[256];
 
 void load_lidt(idt_entry_t *idt, int nentries)
@@ -157,3 +201,131 @@ unsigned exception_error_code(void)
     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
+ */
+
+static gdt_entry_t gdt[6];
+#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));
+}
+
+static void set_idt_task_gate(int vec, u16 sel)
+{
+    idt_entry_t *e = &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];
+		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/lib/x86/desc.h b/lib/x86/desc.h
index 81b8944..2c9db5b 100644
--- a/lib/x86/desc.h
+++ b/lib/x86/desc.h
@@ -2,6 +2,13 @@
 #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
 
 #define ASM_TRY(catch)                                  \
     "movl $0, %%gs:4 \n\t"                              \
@@ -13,8 +20,14 @@ void setup_idt(void);
 #define UD_VECTOR   6
 #define GP_VECTOR   13
 
+#define TSS_MAIN 0x20
+#define TSS_INTR 0x28
+
 unsigned exception_vector(void);
 unsigned exception_error_code(void);
 void set_idt_entry(int vec, void *addr, int dpl);
+void set_gdt_entry(int num, u32 base,  u32 limit, u8 access, u8 gran);
+void set_intr_task_gate(int e, void *fn);
+void print_current_tss_info(void);
 
 #endif
diff --git a/x86/taskswitch2.c b/x86/taskswitch2.c
new file mode 100644
index 0000000..f51cb91
--- /dev/null
+++ b/x86/taskswitch2.c
@@ -0,0 +1,185 @@
+#include "libcflat.h"
+#include "desc.h"
+#include "apic-defs.h"
+#include "apic.h"
+#include "processor.h"
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+static volatile int test_count;
+static volatile unsigned int test_divider;
+
+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;
+}
+
+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;
+}
+
+int main()
+{
+	unsigned int res;
+
+	setup_idt();
+	setup_gdt();
+	setup_tss32();
+
+	/* 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 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);
+
+	printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
+
+	return g_fail != 0;
+}
--
To unsubscribe from this list: send the line "unsubscribe kvm-commits" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [KVM Development]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Walks]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux