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> --- config-i386.mak | 3 +- lib/x86/desc.c | 172 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/x86/desc.h | 13 ++++ x86/taskswitch2.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 372 insertions(+), 1 deletions(-) create mode 100644 x86/taskswitch2.c 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; +} -- 1.7.2.3 -- To unsubscribe from this list: send the line "unsubscribe kvm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html