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