Commit d21b4f12 authored by Gleb Natapov's avatar Gleb Natapov Committed by Avi Kivity
Browse files

Add another task switch test



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: default avatarGleb Natapov <gleb@redhat.com>
Signed-off-by: default avatarAvi Kivity <avi@redhat.com>
parent ae0a920b
......@@ -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
......@@ -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
......@@ -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
#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;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment