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