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