xref: /kvm-unit-tests/x86/taskswitch2.c (revision 5aca024ecf2c01430d0993df439374c46c6f2a29)
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