xref: /kvm-unit-tests/x86/apic.c (revision ae0a920b64bd69e738e8152f582ff12d92565720)
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 eoi(void)
138 {
139     apic_write(APIC_EOI, 0);
140 }
141 
142 static int ipi_count;
143 
144 static void self_ipi_isr(isr_regs_t *regs)
145 {
146     ++ipi_count;
147     eoi();
148 }
149 
150 static void test_self_ipi(void)
151 {
152     int vec = 0xf1;
153 
154     handle_irq(vec, self_ipi_isr);
155     irq_enable();
156     apic_icr_write(APIC_DEST_SELF | APIC_DEST_PHYSICAL | APIC_DM_FIXED | vec,
157                    0);
158     asm volatile ("nop");
159     report("self ipi", ipi_count == 1);
160 }
161 
162 static void set_ioapic_redir(unsigned line, unsigned vec)
163 {
164     ioapic_redir_entry_t e = {
165         .vector = vec,
166         .delivery_mode = 0,
167         .trig_mode = 0,
168     };
169 
170     ioapic_write_redir(line, e);
171 }
172 
173 static void set_irq_line(unsigned line, int val)
174 {
175     asm volatile("out %0, %1" : : "a"((u8)val), "d"((u16)(0x2000 + line)));
176 }
177 
178 static void toggle_irq_line(unsigned line)
179 {
180     set_irq_line(line, 1);
181     set_irq_line(line, 0);
182 }
183 
184 static int g_isr_77;
185 
186 static void ioapic_isr_77(isr_regs_t *regs)
187 {
188     ++g_isr_77;
189     eoi();
190 }
191 
192 static void test_ioapic_intr(void)
193 {
194     handle_irq(0x77, ioapic_isr_77);
195     set_ioapic_redir(0x10, 0x77);
196     toggle_irq_line(0x10);
197     asm volatile ("nop");
198     report("ioapic interrupt", g_isr_77 == 1);
199 }
200 
201 static int g_78, g_66, g_66_after_78;
202 static ulong g_66_rip, g_78_rip;
203 
204 static void ioapic_isr_78(isr_regs_t *regs)
205 {
206     ++g_78;
207     g_78_rip = regs->rip;
208     eoi();
209 }
210 
211 static void ioapic_isr_66(isr_regs_t *regs)
212 {
213     ++g_66;
214     if (g_78)
215         ++g_66_after_78;
216     g_66_rip = regs->rip;
217     eoi();
218 }
219 
220 static void test_ioapic_simultaneous(void)
221 {
222     handle_irq(0x78, ioapic_isr_78);
223     handle_irq(0x66, ioapic_isr_66);
224     set_ioapic_redir(0x10, 0x78);
225     set_ioapic_redir(0x11, 0x66);
226     irq_disable();
227     toggle_irq_line(0x11);
228     toggle_irq_line(0x10);
229     irq_enable();
230     asm volatile ("nop");
231     report("ioapic simultaneous interrupt",
232            g_66 && g_78 && g_66_after_78 && g_66_rip == g_78_rip);
233 }
234 
235 volatile int nmi_counter_private, nmi_counter, nmi_hlt_counter, sti_loop_active;
236 
237 void sti_nop(char *p)
238 {
239     asm volatile (
240 		  ".globl post_sti \n\t"
241 		  "sti \n"
242 		  /*
243 		   * vmx won't exit on external interrupt if blocked-by-sti,
244 		   * so give it a reason to exit by accessing an unmapped page.
245 		   */
246 		  "post_sti: testb $0, %0 \n\t"
247 		  "nop \n\t"
248 		  "cli"
249 		  : : "m"(*p)
250 		  );
251     nmi_counter = nmi_counter_private;
252 }
253 
254 static void sti_loop(void *ignore)
255 {
256     unsigned k = 0;
257 
258     while (sti_loop_active) {
259 	sti_nop((char *)(ulong)((k++ * 4096) % (128 * 1024 * 1024)));
260     }
261 }
262 
263 static void nmi_handler(isr_regs_t *regs)
264 {
265     extern void post_sti(void);
266     ++nmi_counter_private;
267     nmi_hlt_counter += regs->rip == (ulong)post_sti;
268 }
269 
270 static void update_cr3(void *cr3)
271 {
272     write_cr3((ulong)cr3);
273 }
274 
275 static void test_sti_nmi(void)
276 {
277     unsigned old_counter;
278 
279     if (cpu_count() < 2) {
280 	return;
281     }
282 
283     handle_irq(2, nmi_handler);
284     on_cpu(1, update_cr3, (void *)read_cr3());
285 
286     sti_loop_active = 1;
287     on_cpu_async(1, sti_loop, 0);
288     while (nmi_counter < 30000) {
289 	old_counter = nmi_counter;
290 	apic_icr_write(APIC_DEST_PHYSICAL | APIC_DM_NMI | APIC_INT_ASSERT, 1);
291 	while (nmi_counter == old_counter) {
292 	    ;
293 	}
294     }
295     sti_loop_active = 0;
296     report("nmi-after-sti", nmi_hlt_counter == 0);
297 }
298 
299 int main()
300 {
301     setup_vm();
302     smp_init();
303     setup_idt();
304 
305     test_lapic_existence();
306 
307     mask_pic_interrupts();
308     enable_apic();
309     test_enable_x2apic();
310 
311     test_self_ipi();
312 
313     test_ioapic_intr();
314     test_ioapic_simultaneous();
315     test_sti_nmi();
316 
317     printf("\nsummary: %d tests, %d failures\n", g_tests, g_fail);
318 
319     return g_fail != 0;
320 }
321