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