1 #include "libcflat.h" 2 #include "desc.h" 3 #include "apic-defs.h" 4 #include "apic.h" 5 #include "processor.h" 6 #include "vm.h" 7 #include "vmalloc.h" 8 #include "alloc_page.h" 9 #include "delay.h" 10 11 #define MAIN_TSS_SEL (FIRST_SPARE_SEL + 0) 12 #define VM86_TSS_SEL (FIRST_SPARE_SEL + 8) 13 #define CONFORM_CS_SEL (FIRST_SPARE_SEL + 16) 14 15 static volatile int test_count; 16 static volatile unsigned int test_divider; 17 18 static char *fault_addr; 19 static ulong fault_phys; 20 21 void do_pf_tss(ulong *error_code); 22 23 static void nmi_tss(void) 24 { 25 start: 26 printf("NMI task is running\n"); 27 print_current_tss_info(); 28 test_count++; 29 asm volatile ("iret"); 30 goto start; 31 } 32 33 static void de_tss(void) 34 { 35 start: 36 printf("DE task is running\n"); 37 print_current_tss_info(); 38 test_divider = 10; 39 test_count++; 40 asm volatile ("iret"); 41 goto start; 42 } 43 44 static void of_tss(void) 45 { 46 start: 47 printf("OF task is running\n"); 48 print_current_tss_info(); 49 test_count++; 50 asm volatile ("iret"); 51 goto start; 52 } 53 54 static void bp_tss(void) 55 { 56 start: 57 printf("BP task is running\n"); 58 print_current_tss_info(); 59 test_count++; 60 asm volatile ("iret"); 61 goto start; 62 } 63 64 void do_pf_tss(ulong *error_code) 65 { 66 printf("PF task is running %p %lx\n", error_code, *error_code); 67 print_current_tss_info(); 68 if (*error_code == 0x2) /* write access, not present */ 69 test_count++; 70 install_pte(phys_to_virt(read_cr3()), 1, fault_addr, 71 fault_phys | PT_PRESENT_MASK | PT_WRITABLE_MASK, 0); 72 } 73 74 extern void pf_tss(void); 75 76 asm ( 77 "pf_tss: \n\t" 78 "push %esp \n\t" 79 "call do_pf_tss \n\t" 80 "add $4, %esp \n\t" 81 "iret\n\t" 82 "jmp pf_tss\n\t" 83 ); 84 85 static void jmp_tss(void) 86 { 87 start: 88 printf("JMP to task succeeded\n"); 89 print_current_tss_info(); 90 test_count++; 91 asm volatile ("ljmp $" xstr(TSS_MAIN) ", $0"); 92 goto start; 93 } 94 95 static void irq_tss(void) 96 { 97 start: 98 printf("IRQ task is running\n"); 99 print_current_tss_info(); 100 test_count++; 101 asm volatile ("iret"); 102 test_count++; 103 printf("IRQ task restarts after iret.\n"); 104 goto start; 105 } 106 107 static void user_tss(void) 108 { 109 start: 110 printf("Conforming task is running\n"); 111 print_current_tss_info(); 112 test_count++; 113 asm volatile ("iret"); 114 goto start; 115 } 116 117 static void test_kernel_mode_int(void) 118 { 119 unsigned int res; 120 121 /* test that int $2 triggers task gate */ 122 test_count = 0; 123 set_intr_task_gate(2, nmi_tss); 124 printf("Triggering nmi 2\n"); 125 asm volatile ("int $2"); 126 printf("Return from nmi %d\n", test_count); 127 report(test_count == 1, "NMI int $2"); 128 129 /* test that external NMI triggers task gate */ 130 test_count = 0; 131 set_intr_task_gate(2, nmi_tss); 132 printf("Triggering nmi through APIC\n"); 133 apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 0); 134 io_delay(); 135 printf("Return from APIC nmi\n"); 136 report(test_count == 1, "NMI external"); 137 138 /* test that external interrupt triggesr task gate */ 139 test_count = 0; 140 printf("Trigger IRQ from APIC\n"); 141 set_intr_task_gate(0xf0, irq_tss); 142 irq_enable(); 143 apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | APIC_INT_ASSERT | 0xf0, 0); 144 io_delay(); 145 irq_disable(); 146 printf("Return from APIC IRQ\n"); 147 report(test_count == 1, "IRQ external"); 148 149 /* test that HW exception triggesr task gate */ 150 set_intr_task_gate(0, de_tss); 151 printf("Try to devide by 0\n"); 152 asm volatile ("divl %3": "=a"(res) 153 : "d"(0), "a"(1500), "m"(test_divider)); 154 printf("Result is %d\n", res); 155 report(res == 150, "DE exeption"); 156 157 /* test if call HW exeption DE by int $0 triggers task gate */ 158 test_count = 0; 159 set_intr_task_gate(0, de_tss); 160 printf("Call int 0\n"); 161 asm volatile ("int $0"); 162 printf("Return from int 0\n"); 163 report(test_count == 1, "int $0"); 164 165 /* test if HW exception OF triggers task gate */ 166 test_count = 0; 167 set_intr_task_gate(4, of_tss); 168 printf("Call into\n"); 169 asm volatile ("addb $127, %b0\ninto"::"a"(127)); 170 printf("Return from into\n"); 171 report(test_count, "OF exeption"); 172 173 /* test if HW exception BP triggers task gate */ 174 test_count = 0; 175 set_intr_task_gate(3, bp_tss); 176 printf("Call int 3\n"); 177 asm volatile ("int $3"); 178 printf("Return from int 3\n"); 179 report(test_count == 1, "BP exeption"); 180 181 /* 182 * test that PF triggers task gate and error code is placed on 183 * exception task's stack 184 */ 185 fault_addr = alloc_vpage(); 186 fault_phys = (ulong)virt_to_phys(alloc_page()); 187 test_count = 0; 188 set_intr_task_gate(14, pf_tss); 189 printf("Access unmapped page\n"); 190 *fault_addr = 0; 191 printf("Return from pf tss\n"); 192 report(test_count == 1, "PF exeption"); 193 } 194 195 static void test_gdt_task_gate(void) 196 { 197 /* test that calling a task by lcall works */ 198 test_count = 0; 199 tss_intr.eip = (u32)irq_tss; 200 printf("Calling task by lcall\n"); 201 /* hlt opcode is 0xf4 I use destination IP 0xf4f4f4f4 to catch 202 incorrect instruction length calculation */ 203 asm volatile("lcall $" xstr(TSS_INTR) ", $0xf4f4f4f4"); 204 printf("Return from call\n"); 205 report(test_count == 1, "lcall"); 206 207 /* call the same task again and check that it restarted after iret */ 208 test_count = 0; 209 asm volatile("lcall $" xstr(TSS_INTR) ", $0xf4f4f4f4"); 210 report(test_count == 2, "lcall2"); 211 212 /* test that calling a task by ljmp works */ 213 test_count = 0; 214 tss_intr.eip = (u32)jmp_tss; 215 printf("Jumping to a task by ljmp\n"); 216 asm volatile ("ljmp $" xstr(TSS_INTR) ", $0xf4f4f4f4"); 217 printf("Jump back succeeded\n"); 218 report(test_count == 1, "ljmp"); 219 } 220 221 static void test_vm86_switch(void) 222 { 223 static tss32_t main_tss; 224 static tss32_t vm86_tss; 225 226 u8 *vm86_start; 227 228 /* Write a 'ud2' instruction somewhere below 1 MB */ 229 vm86_start = (void*) 0x42000; 230 vm86_start[0] = 0x0f; 231 vm86_start[1] = 0x0b; 232 233 /* Main TSS */ 234 set_gdt_entry(MAIN_TSS_SEL, (u32)&main_tss, sizeof(tss32_t) - 1, 0x89, 0); 235 ltr(MAIN_TSS_SEL); 236 main_tss = (tss32_t) { 237 .prev = VM86_TSS_SEL, 238 .cr3 = read_cr3(), 239 }; 240 241 /* VM86 TSS (marked as busy, so we can iret to it) */ 242 set_gdt_entry(VM86_TSS_SEL, (u32)&vm86_tss, sizeof(tss32_t) - 1, 0x8b, 0); 243 vm86_tss = (tss32_t) { 244 .eflags = 0x20002, 245 .cr3 = read_cr3(), 246 .eip = (u32) vm86_start & 0x0f, 247 .cs = (u32) vm86_start >> 4, 248 .ds = 0x1234, 249 .es = 0x2345, 250 }; 251 252 /* Setup task gate to main TSS for #UD */ 253 set_idt_task_gate(6, MAIN_TSS_SEL); 254 255 /* Jump into VM86 task with iret, #UD lets it come back immediately */ 256 printf("Switch to VM86 task and back\n"); 257 asm volatile( 258 "pushf\n" 259 "orw $0x4000, (%esp)\n" 260 "popf\n" 261 "iret\n" 262 ); 263 report(1, "VM86"); 264 } 265 266 #define IOPL_SHIFT 12 267 268 static void test_conforming_switch(void) 269 { 270 /* test lcall with conforming segment, cs.dpl != cs.rpl */ 271 test_count = 0; 272 273 tss_intr.cs = CONFORM_CS_SEL | 3; 274 tss_intr.eip = (u32)user_tss; 275 tss_intr.ss = USER_DS; 276 tss_intr.ds = tss_intr.gs = tss_intr.es = tss_intr.fs = tss_intr.ss; 277 tss_intr.eflags |= 3 << IOPL_SHIFT; 278 set_gdt_entry(CONFORM_CS_SEL, 0, 0xffffffff, 0x9f, 0xc0); 279 asm volatile("lcall $" xstr(TSS_INTR) ", $0xf4f4f4f4"); 280 report(test_count == 1, "lcall with cs.rpl != cs.dpl"); 281 } 282 283 int main(void) 284 { 285 setup_vm(); 286 setup_tss32(); 287 288 test_gdt_task_gate(); 289 test_kernel_mode_int(); 290 test_vm86_switch(); 291 test_conforming_switch(); 292 293 return report_summary(); 294 } 295