Fix high interrupt ISRs
[viridis.git] / kernel / apic.c
1 /* vim: set ts=4 sw=4 sts=4 et : */
2
3 /* Support for the built-in xAPIC, or x2APIC. We don't bother with the old 386
4  * PIC except to disable it because xAPIC is a foundation technology of the
5  * AMD64 arch.
6  *
7  * While I'm reticent to implement too much "old" hardware, the interfaces here
8  * are very similar, and since xAPIC was present on all early x86_64 hardware
9  * we might as well support it as a baseline.
10  *
11  * Extant differences:
12  *
13  * - x2APIC used via MSRs, not MMIO (although offsets are related)
14  * - x2APIC has a full 32 bit ID, instead of 8 bits
15  * - x2APIC ICR is written as a single 64 bit MSR, instead of 2x 32 bit MMIOs
16  * - x2APIC ICR with expanded ID destination
17  * - x2APIC ICR has no busy bit, will accept writes in order
18  */
19
20 #include <kernel.h>
21 #include <vsalloc.h>
22 #include <map.h>
23 #include <apic.h>
24
25 #include <asm/cpuid.h>
26 #include <asm/realmode.h>
27 #include <asm/barrier.h>
28 #include <asm/mmio.h>
29 #include <asm/msr.h>
30
31 static volatile u64 *mmio_mem = NULL;
32
33 static void apic_init_common(void)
34 {
35     apic->write(APIC_SVR, SVR_APIC_EN);
36     apic->write(APIC_LVT3, LVT_VECTOR(34));
37 }
38
39 static void apic_start_timer(void)
40 {
41     u32 lvt;
42
43     /* Divide by 1 */
44     apic->write(APIC_TIMER_DIV, 0x11);
45
46     /* Setup a periodic timer interrupt */
47     lvt = apic->read(APIC_LVT_TIMER);
48
49     lvt &= ~LVT_MASKED;
50     lvt |= LVT_VECTOR(32) | LVT_TIMER_PERIODIC;
51
52     apic->write(APIC_LVT_TIMER, lvt);
53
54     /* Set the initial count, which will start the timer */
55     apic->write(APIC_TIMER_INIT_CT, 0x1000);
56 }
57
58 static void apic_eoi(void)
59 {
60     /* Allow another interrupt to come through when we re-enable
61      * them (exiting through exception)
62      */
63     apic->write(APIC_EOI, 0);
64 }
65
66 static void apic_wake_secondary(u8 apic_id)
67 {
68     u32 lo;
69
70     lo = ICRL_MT(MT_INIT) | ICRL_ASSERT | ICRL_TRIGGER_LEVEL;
71
72     apic->write_icr(apic_id, lo);
73
74     apic->wait();
75
76     lo = ICRL_MT(MT_INIT) | ICRL_TRIGGER_LEVEL;
77
78     apic->write_icr(apic_id, lo);
79
80     apic->wait();
81
82     lo = ICRL_MT(MT_STARTUP) | ICRL_ASSERT | (REALMODE_PA >> PAGE_SHIFT);
83
84     apic->write_icr(apic_id, lo);
85
86     apic->wait();
87
88     lo = ICRL_MT(MT_STARTUP) | ICRL_ASSERT | (REALMODE_PA >> PAGE_SHIFT);
89
90     apic->write_icr(apic_id, lo);
91
92     apic->wait();
93 }
94
95 static void xapic_init(void)
96 {
97     u64 msr;
98
99     /* Make sure the APIC is actually enabled */
100     msr = rdmsr(MSR_IA32_APIC_BASE);
101     wrmsr(MSR_IA32_APIC_BASE, msr | XAPIC_ENABLE);
102
103     /* Get a nocache virtual memory of the base memory */
104     mmio_mem = vsalloc("APIC", 0, 1);
105
106     map_page_nocache((u64) mmio_mem, msr & ~(0xFFFUL));
107
108     apic_init_common();
109 }
110
111 static void xapic_mmio_write(u32 offset, u32 val)
112 {
113     mmio_write(mmio_mem, MMIO_OFFSET(offset), val);
114 }
115
116 static u32 xapic_mmio_read(u32 offset)
117 {
118     return mmio_read(mmio_mem, MMIO_OFFSET(offset));
119 }
120
121 static void xapic_ipi_wait(void)
122 {
123     while (apic->read(APIC_ICRL) & ICRL_BUSY);
124 }
125
126 static void xapic_write_icr(u32 dest, u32 val)
127 {
128     apic->write(APIC_ICRH, ICRH_DEST(dest));
129     apic->write(APIC_ICRL, val);
130 }
131
132 static u32 xapic_get_id(void)
133 {
134     return ID(apic->read(APIC_ID));
135 }
136
137 struct apic xapic = {
138     .init = xapic_init,
139     .eoi = apic_eoi,
140     .wake_secondary = apic_wake_secondary,
141     .start_timer = apic_start_timer,
142     .get_id = xapic_get_id,
143     .write = xapic_mmio_write,
144     .write_icr = xapic_write_icr,
145     .read = xapic_mmio_read,
146     .wait = xapic_ipi_wait,
147 };
148
149 static void x2apic_init(void)
150 {
151     u64 msr;
152
153     /* Make sure the APIC is actually enabled */
154     msr = rdmsr(MSR_IA32_APIC_BASE);
155
156     /* Need EXTD flag to use MSR interface */
157     wrmsr(MSR_IA32_APIC_BASE, msr | XAPIC_ENABLE | XAPIC_EXTD);
158
159     apic_init_common();
160 }
161
162 static void x2apic_msr_write(u32 offset, u32 val)
163 {
164     wrmsr(MSR_OFFSET(offset), val);
165 }
166
167 static u32 x2apic_msr_read(u32 offset)
168 {
169     return rdmsr(MSR_OFFSET(offset));
170 }
171
172 static void x2apic_ipi_wait(void)
173 {
174     return;
175 }
176
177 static void x2apic_write_icr(u32 dest, u32 val)
178 {
179     wrmsr(MSR_OFFSET(APIC_ICRL), ((u64) dest << 32) | val);
180 }
181
182 static u32 x2apic_get_id(void)
183 {
184     return apic->read(APIC_ID);
185 }
186
187 struct apic x2apic = {
188     .init = x2apic_init,
189     .eoi = apic_eoi,
190     .wake_secondary = apic_wake_secondary,
191     .start_timer = apic_start_timer,
192     .get_id = x2apic_get_id,
193     .read = x2apic_msr_read,
194     .write = x2apic_msr_write,
195     .write_icr = x2apic_write_icr,
196     .wait = x2apic_ipi_wait,
197 };
198
199 int apic_init(void)
200 {
201     u32 regs[4];
202
203     cpuid(0x1, regs);
204
205     if (regs[2] & CPUID_X2APIC) {
206         printk("x2apic detected\n");
207         apic = &x2apic;
208     } else if (regs[3] & CPUID_LAPIC) {
209         printk("xapic detected\n");
210         apic = &xapic;
211     } else {
212         printk("no APIC detected\n");
213         return -1;
214     }
215
216     apic->init();
217
218     return 0;
219 }