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