furix
cute os
arch/x86_64/lapic.c
5201 bytes raw
1#include <lapic.h>
2
3#include <io.h>
4#include <types.h>
5
6#define IA32_APIC_BASE 0x1B
7
8#define APIC_BASE_ENABLE (1ull << 11)
9#define APIC_BASE_ADDR_MASK 0xFFFFF000ull
10#define APIC_DEFAULT_BASE 0xFEE00000u
11
12#define APIC_REG_ID 0x20
13#define APIC_REG_VERSION 0x30
14#define APIC_REG_EOI 0xB0
15#define APIC_REG_SPURIOUS 0xF0
16#define APIC_REG_TIMER 0x320
17
18/* bit 16 of the timer control register masks it */
19#define APIC_TIMER_MASKED (1u << 16)
20#define APIC_REG_ICR 0x300
21#define APIC_REG_ICR_HIGH 0x310
22
23#define ICR_DELIVERY_STATUS 0x1000u /* bit 12: set while the IPI is in flight */
24#define ICR_TRIGGER_MODE 0x4000u /* bit 14: level vs edge */
25#define ICR_SHORTHAND_SHIFT 18
26
27/* bounded so a lost IPI surfaces as a failed bringup, never a hang */
28#define ICR_WAIT_SPINS 1000000u
29
30#define SPURIOUS_VECTOR 0xFF
31
32static volatile uint32_t *apic_reg(uint32_t reg) {
33 return (volatile uint32_t *)(uint64_t)(lapic_base() + reg);
34}
35
36void lapic_enable(void) {
37 uint64_t base = rdmsr(IA32_APIC_BASE);
38
39 /*
40 * ORDER MATTERS: mask the timer BEFORE enabling the LAPIC.
41 *
42 * firmware leaves the LAPIC timer running on whatever vector its divide
43 * config selects, which is 0x20 here. enabling the LAPIC first opens the
44 * delivery path, so that timer can latch vector 0x20 in the ISR before the
45 * mask below lands. IF is clear at this point so the request is held pending
46 * rather than taken, and is delivered at the first `sti` much later, which
47 * is the self-IPI test. that lands on the default gate and halts the kernel,
48 * so the failure reads as "unexpected interrupt during the self-IPI test"
49 * with nothing pointing at the timer.
50 */
51 *apic_reg(APIC_REG_TIMER) = APIC_TIMER_MASKED;
52
53 *apic_reg(APIC_REG_SPURIOUS) = (uint32_t)(SPURIOUS_VECTOR | (1u << 8));
54
55 if ((base & APIC_BASE_ENABLE) == 0) {
56 wrmsr(IA32_APIC_BASE, base | APIC_BASE_ENABLE);
57 }
58
59 /*
60 * drain anything firmware already had in flight. masking the LVT stops new
61 * requests but does not retract one already pending, and there is no way to
62 * read the ISR to find out what it was. acknowledge blindly: an EOI with
63 * nothing outstanding is a no-op.
64 */
65 lapic_eoi();
66}
67
68uint32_t lapic_base(void) {
69 uint32_t base = (uint32_t)(rdmsr(IA32_APIC_BASE) & APIC_BASE_ADDR_MASK);
70
71 if (base == 0) {
72 base = APIC_DEFAULT_BASE;
73 }
74 return base;
75}
76
77uint32_t lapic_id(void) {
78 /*
79 * only the low 8 bits of the xAPIC ID register are meaningful; bits above
80 * that read back as zero on real hardware but are not architecturally
81 * guaranteed to, so mask rather than trust the read. widened from uint8_t
82 * because an x2APIC MADT reports 32-bit IDs; see lapic_ipi_send_flags for
83 * what happens to those.
84 */
85 return (uint32_t)(*apic_reg(APIC_REG_ID) >> 24) & 0xffu;
86}
87
88void lapic_eoi(void) {
89 *apic_reg(APIC_REG_EOI) = 0;
90}
91
92bool lapic_ipi_wait(void) {
93 uint32_t spins = ICR_WAIT_SPINS;
94
95 while ((*apic_reg(APIC_REG_ICR) & ICR_DELIVERY_STATUS) != 0) {
96 if (spins-- == 0) {
97 return false;
98 }
99 }
100 return true;
101}
102
103/*
104 * returns 0 if the IPI was sent, non-zero if the destination could not be
105 * addressed at all (an x2APIC ID beyond the xAPIC ICR's 8-bit field). nothing
106 * is delivered in that case.
107 */
108uint32_t lapic_ipi_send_flags(uint32_t delivery, uint32_t shorthand, uint32_t vector,
109 uint32_t apic_id, uint32_t flags) {
110 /*
111 * the delivery-status bit reads from the high half of the 64-bit ICR, and
112 * the destination field only applies when shorthand is unused, so it is
113 * harmless to always program ICR_HIGH.
114 */
115 if (shorthand == LAPIC_DEST) {
116 /*
117 * the xAPIC destination field is only bits 24..31 of ICR_HIGH, so it can
118 * name at most 256 processors. an ID above that comes from an x2APIC MADT
119 * entry and would be silently truncated, sending INIT to processor 0 and
120 * resetting the BSP instead of the intended AP.
121 *
122 * addressing those needs IA32_X2APIC_ICR (MSR 0x8B), which is only
123 * reachable once x2APIC mode is enabled in IA32_APIC_BASE. that is not done
124 * yet, so refuse loudly instead of misdirecting the IPI. callers treat a
125 * non-zero return as "this AP was not started".
126 */
127 if (apic_id > 0xffu) {
128 return 1;
129 }
130 *apic_reg(APIC_REG_ICR_HIGH) = apic_id << 24;
131 } else {
132 *apic_reg(APIC_REG_ICR_HIGH) = 0;
133 }
134
135 *apic_reg(APIC_REG_ICR) = vector |
136 (delivery << 8) |
137 (shorthand << ICR_SHORTHAND_SHIFT) |
138 flags;
139
140 lapic_ipi_wait();
141 return 0;
142}
143
144uint32_t lapic_ipi_send(uint32_t delivery, uint32_t shorthand, uint32_t vector,
145 uint32_t apic_id) {
146 return lapic_ipi_send_flags(delivery, shorthand, vector, apic_id, 0);
147}
148
149uint32_t lapic_ipi_init_assert(uint32_t apic_id) {
150 return lapic_ipi_send_flags(LAPIC_IPI_INIT, LAPIC_DEST, 0, apic_id, ICR_TRIGGER_MODE);
151}
152
153uint32_t lapic_ipi_init_deassert(uint32_t apic_id) {
154 return lapic_ipi_send_flags(LAPIC_IPI_INIT, LAPIC_DEST, 0, apic_id, 0);
155}
156
157uint32_t lapic_ipi_sipi(uint32_t apic_id, uint8_t vector) {
158 return lapic_ipi_send_flags(LAPIC_IPI_STARTUP, LAPIC_DEST, vector, apic_id, 0);
159}