1#include <acpi/lapic.h>
2#include <asm.h>
3#include <cpuid_defs.h>
4#include <drivers/mmio.h>
5#include <enforce.h>
6#include <irq/irq.h>
7#include <log.h>
8#include <mem/page.h>
9
10uint32_t cc_mem_io *lapic;
11bool x2apic_enabled = false;
12LOG_HANDLE_DEFINE_PRINT_STATIC(lapic);
13
14void lapic_init(void) {
15 uintptr_t lapic_phys = rdmsr(IA32_APIC_BASE_MSR) & IA32_APIC_BASE_MASK;
16 lapic = mmio_map(phys: lapic_phys, PAGE_SIZE);
17}
18
19static bool cpu_has_x2apic(void) {
20 uint32_t eax, ebx, ecx, edx;
21 cpuid_count(CPUID_LEAF_FEATURES, subleaf: 0, eax: &eax, ebx: &ebx, ecx: &ecx, edx: &edx);
22 return ecx & CPUID_ECX_X2APIC;
23}
24
25void x2apic_init(void) {
26 if (!cpu_has_x2apic())
27 return;
28
29 x2apic_enabled = true;
30 uint64_t apic_base;
31 apic_base = rdmsr(IA32_APIC_BASE);
32 apic_base |= APIC_X2APIC_ENABLE;
33 wrmsr(IA32_APIC_BASE, value: apic_base);
34 log_info_global(LOG_HANDLE(lapic), "X2APIC enabled");
35}
36
37uint32_t x2apic_get_id(void) {
38 return rdmsr(IA32_X2APIC_ID) & 0xFFFFFFFF;
39}
40
41uint64_t lapic_get_id(void) {
42 uint32_t lapic_id_raw = lapic_read(LAPIC_REG_ID);
43 uint64_t cpu = (lapic_id_raw >> 24) & 0xFF;
44 return cpu;
45}
46
47uint32_t lapic_this_id(void) {
48 return cpu_has_x2apic() ? x2apic_get_id() : lapic_get_id();
49}
50
51uint8_t lapic_dest_8bit(uint32_t apic_id) {
52 enforce(apic_id <= UINT8_MAX, UNSUPPORTED,
53 "APIC ID %u needs interrupt remapping", apic_id);
54 return (uint8_t) apic_id;
55}
56
57static void lapic_eoi(struct irq_desc *unused) {
58 cc_ignore(unused);
59 lapic_write(LAPIC_REG_EOI, val: 0);
60}
61
62static struct irq_chip lapic_irq_chip = {
63 .eoi = lapic_eoi,
64 .mask = NULL,
65 .unmask = NULL,
66 .set_affinity = NULL,
67 .set_rate_limit = NULL,
68 .name = "lapic",
69};
70
71struct irq_chip *lapic_get_chip(void) {
72 return &lapic_irq_chip;
73}
74
75/* ===== ICR ===== */
76
77static void x2apic_icr_write(uint32_t dest, uint32_t lo) {
78 cpu_full_fence();
79 wrmsr(IA32_X2APIC_ICR, value: ((uint64_t) dest << 32) | lo);
80}
81
82/* SDM forbids writing ICR whilst Delivery Status is Send Pending */
83static bool xapic_icr_busy(void) {
84 return lapic_read(LAPIC_ICR_LOW) & LAPIC_IPI_IN_FLIGHT;
85}
86
87/* High must be written before low, the low write sends */
88static void xapic_icr_write(uint32_t dest, uint32_t lo) {
89 lapic_write(LAPIC_ICR_HIGH, val: dest << LAPIC_DEST_SHIFT);
90 lapic_write(LAPIC_ICR_LOW, val: lo);
91}
92
93/* IRQs off rather than an IRQL raise so the NMI/panic path can use this */
94void lapic_icr_write(uint32_t dest, uint32_t lo) {
95 if (x2apic_enabled)
96 return x2apic_icr_write(dest, lo);
97
98 bool iflag = irq_disable_save();
99 while (xapic_icr_busy())
100 cpu_pause();
101
102 xapic_icr_write(dest, lo);
103 irq_restore(was_enabled: iflag);
104}
105
106bool lapic_icr_try_write(uint32_t dest, uint32_t lo) {
107 if (x2apic_enabled) {
108 x2apic_icr_write(dest, lo);
109 return true;
110 }
111
112 bool iflag = irq_disable_save();
113 bool busy = xapic_icr_busy();
114 if (!busy)
115 xapic_icr_write(dest, lo);
116
117 irq_restore(was_enabled: iflag);
118 return !busy;
119}
120
121uint32_t lapic_icr_nmi_save(void) {
122 if (x2apic_enabled || !lapic)
123 return 0;
124
125 return lapic_read(LAPIC_ICR_HIGH);
126}
127
128/* Wait on the ICR */
129void lapic_icr_nmi_restore(uint32_t high) {
130 if (x2apic_enabled || !lapic)
131 return;
132
133 while (xapic_icr_busy())
134 cpu_pause();
135
136 lapic_write(LAPIC_ICR_HIGH, val: high);
137}
138