xref: /kvm-unit-tests/x86/apic.c (revision e7c37968d5f9fd45e6e2f88b82562fd89128e365)
1 #include "libcflat.h"
2 #include "apic.h"
3 #include "vm.h"
4 #include "smp.h"
5 #include "desc.h"
6 
7 typedef struct {
8     ulong regs[sizeof(ulong)*2];
9     ulong func;
10     ulong rip;
11     ulong cs;
12     ulong rflags;
13 } isr_regs_t;
14 
15 #ifdef __x86_64__
16 #  define R "r"
17 #else
18 #  define R "e"
19 #endif
20 
21 extern char isr_entry_point[];
22 
23 asm (
24     "isr_entry_point: \n"
25 #ifdef __x86_64__
26     "push %r15 \n\t"
27     "push %r14 \n\t"
28     "push %r13 \n\t"
29     "push %r12 \n\t"
30     "push %r11 \n\t"
31     "push %r10 \n\t"
32     "push %r9  \n\t"
33     "push %r8  \n\t"
34 #endif
35     "push %"R "di \n\t"
36     "push %"R "si \n\t"
37     "push %"R "bp \n\t"
38     "push %"R "sp \n\t"
39     "push %"R "bx \n\t"
40     "push %"R "dx \n\t"
41     "push %"R "cx \n\t"
42     "push %"R "ax \n\t"
43 #ifdef __x86_64__
44     "mov %rsp, %rdi \n\t"
45     "callq *8*16(%rsp) \n\t"
46 #else
47     "push %esp \n\t"
48     "calll *4+4*8(%esp) \n\t"
49     "add $4, %esp \n\t"
50 #endif
51     "pop %"R "ax \n\t"
52     "pop %"R "cx \n\t"
53     "pop %"R "dx \n\t"
54     "pop %"R "bx \n\t"
55     "pop %"R "bp \n\t"
56     "pop %"R "bp \n\t"
57     "pop %"R "si \n\t"
58     "pop %"R "di \n\t"
59 #ifdef __x86_64__
60     "pop %r8  \n\t"
61     "pop %r9  \n\t"
62     "pop %r10 \n\t"
63     "pop %r11 \n\t"
64     "pop %r12 \n\t"
65     "pop %r13 \n\t"
66     "pop %r14 \n\t"
67     "pop %r15 \n\t"
68 #endif
69 #ifdef __x86_64__
70     "add $8, %rsp \n\t"
71     "iretq \n\t"
72 #else
73     "add $4, %esp \n\t"
74     "iretl \n\t"
75 #endif
76     );
77 
78 static int g_fail;
79 static int g_tests;
80 
81 static void report(const char *msg, int pass)
82 {
83     ++g_tests;
84     printf("%s: %s\n", msg, (pass ? "PASS" : "FAIL"));
85     if (!pass)
86         ++g_fail;
87 }
88 
89 static void test_lapic_existence(void)
90 {
91     u32 lvr;
92 
93     lvr = apic_read(APIC_LVR);
94     printf("apic version: %x\n", lvr);
95     report("apic existence", (u16)lvr == 0x14);
96 }
97 
98 #define MSR_APIC_BASE 0x0000001b
99 
100 void test_enable_x2apic(void)
101 {
102     if (enable_x2apic()) {
103         printf("x2apic enabled\n");
104     } else {
105         printf("x2apic not detected\n");
106     }
107 }
108 
109 static void handle_irq(unsigned vec, void (*func)(isr_regs_t *regs))
110 {
111     u8 *thunk = vmalloc(50);
112 
113     set_idt_entry(vec, thunk, 0);
114 
115 #ifdef __x86_64__
116     /* sub $8, %rsp */
117     *thunk++ = 0x48; *thunk++ = 0x83; *thunk++ = 0xec; *thunk++ = 0x08;
118     /* mov $func_low, %(rsp) */
119     *thunk++ = 0xc7; *thunk++ = 0x04; *thunk++ = 0x24;
120     *(u32 *)thunk = (ulong)func; thunk += 4;
121     /* mov $func_high, %(rsp+4) */
122     *thunk++ = 0xc7; *thunk++ = 0x44; *thunk++ = 0x24; *thunk++ = 0x04;
123     *(u32 *)thunk = (ulong)func >> 32; thunk += 4;
124     /* jmp isr_entry_point */
125     *thunk ++ = 0xe9;
126     *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4);
127 #else
128     /* push $func */
129     *thunk++ = 0x68;
130     *(u32 *)thunk = (ulong)func;
131     /* jmp isr_entry_point */
132     *thunk ++ = 0xe9;
133     *(u32 *)thunk = (ulong)isr_entry_point - (ulong)(thunk + 4);
134 #endif
135 }
136 
137 static void irq_disable(void)
138 {
139     asm volatile("cli");
140 }
141 
142 static void irq_enable(void)
143 {
144     asm volatile("sti");
145 }
146 
147 static void eoi(void)
148 {
149     apic_write(APIC_EOI, 0);
150 }
151 
152 static int ipi_count;
153 
154 static void self_ipi_isr(isr_regs_t *regs)
155 {
156     ++ipi_count;
157     eoi();
158 }
159 
160 static void test_self_ipi(void)
161 {
162     int vec = 0xf1;
163 
164     handle_irq(vec, self_ipi_isr);
165     irq_enable();
166     apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | vec,
167                    0);
168     asm volatile ("nop");
169     report("self ipi", ipi_count == 1);
170 }
171 
172 static void set_ioapic_redir(unsigned line, unsigned vec)
173 {
174     ioapic_redir_entry_t e = {
175         .vector = vec,
176         .delivery_mode = 0,
177         .trig_mode = 0,
178     };
179 
180     ioapic_write_redir(line, e);
181 }
182 
183 static void set_irq_line(unsigned line, int val)
184 {
185     asm volatile("out %0, %1" : : "a"((u8)val), "d"((u16)(0x2000 + line)));
186 }
187 
188 static void toggle_irq_line(unsigned line)
189 {
190     set_irq_line(line, 1);
191     set_irq_line(line, 0);
192 }
193 
194 static int g_isr_77;
195 
196 static void ioapic_isr_77(isr_regs_t *regs)
197 {
198     ++g_isr_77;
199     eoi();
200 }
201 
202 static void test_ioapic_intr(void)
203 {
204     handle_irq(0x77, ioapic_isr_77);
205     set_ioapic_redir(0x10, 0x77);
206     toggle_irq_line(0x10);
207     asm volatile ("nop");
208     report("ioapic interrupt", g_isr_77 == 1);
209 }
210 
211 static int g_78, g_66, g_66_after_78;
212 static ulong g_66_rip, g_78_rip;
213 
214 static void ioapic_isr_78(isr_regs_t *regs)
215 {
216     ++g_78;
217     g_78_rip = regs->rip;
218     eoi();
219 }
220 
221 static void ioapic_isr_66(isr_regs_t *regs)
222 {
223     ++g_66;
224     if (g_78)
225         ++g_66_after_78;
226     g_66_rip = regs->rip;
227     eoi();
228 }
229 
230 static void test_ioapic_simultaneous(void)
231 {
232     handle_irq(0x78, ioapic_isr_78);
233     handle_irq(0x66, ioapic_isr_66);
234     set_ioapic_redir(0x10, 0x78);
235     set_ioapic_redir(0x11, 0x66);
236     irq_disable();
237     toggle_irq_line(0x11);
238     toggle_irq_line(0x10);
239     irq_enable();
240     asm volatile ("nop");
241     report("ioapic simultaneous interrupt",
242            g_66 && g_78 && g_66_after_78 && g_66_rip == g_78_rip);
243 }
244 
245 volatile int nmi_counter_private, nmi_counter, nmi_hlt_counter, sti_loop_active;
246 
247 void sti_nop(char *p)
248 {
249     asm volatile (
250 		  ".globl post_sti \n\t"
251 		  "sti \n"
252 		  /*
253 		   * vmx won't exit on external interrupt if blocked-by-sti,
254 		   * so give it a reason to exit by accessing an unmapped page.
255 		   */
256 		  "post_sti: testb $0, %0 \n\t"
257 		  "nop \n\t"
258 		  "cli"
259 		  : : "m"(*p)
260 		  );
261     nmi_counter = nmi_counter_private;
262 }
263 
264 static void sti_loop(void *ignore)
265 {
266     unsigned k = 0;
267 
268     while (sti_loop_active) {
269 	sti_nop((char *)(ulong)((k++ * 4096) % (128 * 1024 * 1024)));
270     }
271 }
272 
273 static void nmi_handler(isr_regs_t *regs)
274 {
275     extern void post_sti(void);
276     ++nmi_counter_private;
277     nmi_hlt_counter += regs->rip == (ulong)post_sti;
278 }
279 
280 static void update_cr3(void *cr3)
281 {
282     write_cr3((ulong)cr3);
283 }
284 
285 static void test_sti_nmi(void)
286 {
287     unsigned old_counter;
288 
289     if (cpu_count() < 2) {
290 	return;
291     }
292 
293     handle_irq(2, nmi_handler);
294     on_cpu(1, update_cr3, (void *)read_cr3());
295 
296     sti_loop_active = 1;
297     on_cpu_async(1, sti_loop, 0);
298     while (nmi_counter < 30000) {
299 	old_counter = nmi_counter;
300 	apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 1);
301 	while (nmi_counter == old_counter) {
302 	    ;
303 	}
304     }
305     sti_loop_active = 0;
306     report("nmi-after-sti", nmi_hlt_counter == 0);
307 }
308 
309 int main()
310 {
311     setup_vm();
312     smp_init();
313     setup_idt();
314 
315     test_lapic_existence();
316 
317     mask_pic_interrupts();
318     enable_apic();
319     test_enable_x2apic();
320 
321     test_self_ipi();
322 
323     test_ioapic_intr();
324     test_ioapic_simultaneous();
325     test_sti_nmi();
326 
327     printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
328 
329     return g_fail != 0;
330 }
331