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