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