xref: /kvm-unit-tests/x86/taskswitch2.c (revision cd5f2fb4ad641c51fe0f1a85264dc3f6ede6e131)
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 
nmi_tss(void)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 
de_tss(void)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 
of_tss(void)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 
bp_tss(void)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 
do_pf_tss(ulong * error_code)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 
jmp_tss(void)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 
irq_tss(void)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 
user_tss(void)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 
test_kernel_mode_int(void)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 	sti();
143 	apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | APIC_INT_ASSERT | 0xf0, 0);
144 	io_delay();
145 	cli();
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 divide 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 exception");
156 
157 	/* test if call HW exception 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 exception");
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 exception");
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 exception");
193 }
194 
test_gdt_task_gate(void)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 
test_vm86_switch(void)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_pass("VM86");
264 }
265 
266 #define IOPL_SHIFT 12
267 
test_conforming_switch(void)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 
main(void)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