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