1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include <mos/mm/paging/paging.h>
4#include <mos/mm/physical/pmm.h>
5#include <mos/mos_global.h>
6#include <mos/platform/platform.h>
7#include <mos/syslog/printk.h>
8#include <mos/x86/acpi/madt.h>
9#include <mos/x86/cpu/cpu.h>
10#include <mos/x86/cpu/cpuid.h>
11#include <mos/x86/interrupt/apic.h>
12#include <mos/x86/mm/paging_impl.h>
13#include <mos/x86/x86_platform.h>
14
15#define APIC_REG_LAPIC_VERSION 0x30
16#define APIC_REG_PRIO_TASK 0x80
17#define APIC_REG_PRIO_ARBITRATION 0x90
18#define APIC_REG_PRIO_PROCESSOR 0xA0
19#define APIC_REG_EOI 0xB0
20#define APIC_REG_REMOTE_READ 0xC0
21#define APIC_REG_LOGICAL_DEST 0xD0
22#define APIC_REG_DEST_FORMAT 0xE0
23#define APIC_REG_SPURIOUS_INTR_VEC 0xF0
24#define APIC_REG_ERROR_STATUS 0x280
25#define APIC_REG_TIMER_INITIAL_COUNT 0x380
26#define APIC_REG_TIMER_CURRENT_COUNT 0x390
27#define APIC_REG_TIMER_DIVIDE_CONFIG 0x3E0
28
29#define APIC_REG_LVT_CMCI_INTR 0x2F0
30#define APIC_REG_LVT_TIMER 0x320
31#define APIC_REG_LVT_THERMAL_SENSOR 0x330
32#define APIC_REG_LVT_PERF_MON_CTR 0x340
33#define APIC_REG_LVT_LINT0 0x350
34#define APIC_REG_LVT_LINT1 0x360
35#define APIC_REG_LVT_ERROR 0x370
36
37#define APIC_IN_SERVICE_REG_BEGIN 0x100
38#define APIC_IN_SERVICE_REG_END 0x170
39
40#define APIC_TRIGGER_MODE_REG_BEGIN 0x180
41#define APIC_TRIGGER_MODE_REG_END 0x1F0
42
43#define APIC_TRIGGER_MODE_REG_TMR_BEGIN 0x180
44#define APIC_TRIGGER_MODE_REG_TMR_END 0x1F0
45
46#define APIC_INTERRUPT_REQUEST_REG_BEGIN 0x200
47#define APIC_INTERRUPT_REQUEST_REG_END 0x270
48
49#define APIC_INTERRUPT_COMMAND_REG_BEGIN 0x300
50#define APIC_INTERRUPT_COMMAND_REG_END 0x310
51
52#define IA32_APIC_BASE_MSR 0x1B
53
54static ptr_t lapic_regs = 0;
55
56u32 lapic_read32(u32 offset)
57{
58 MOS_ASSERT(lapic_regs);
59 pr_dinfo2(x86_lapic, "reading reg: %x, ptr: " PTR_FMT, offset, lapic_regs + offset);
60 return *(volatile u32 *) (lapic_regs + offset);
61}
62
63u64 lapic_read64(u32 offset)
64{
65 MOS_ASSERT(lapic_regs);
66 pr_dinfo2(x86_lapic, "reading reg: %x, ptr: " PTR_FMT, offset, lapic_regs + offset);
67 const u32 high = *(volatile u32 *) (lapic_regs + offset + 0x10);
68 const u32 low = *(volatile u32 *) (lapic_regs + offset);
69 return ((u64) high << 32) | low;
70}
71
72void lapic_write32(u32 offset, u32 value)
73{
74 MOS_ASSERT(lapic_regs);
75 pr_dinfo2(x86_lapic, "writing reg: %x, value: 0x%.8x, ptr: " PTR_FMT, offset, value, lapic_regs + offset);
76 *(volatile u32 *) (lapic_regs + offset) = value;
77}
78
79void lapic_write64(u32 offset, u64 value)
80{
81 MOS_ASSERT(lapic_regs);
82 pr_dinfo2(x86_lapic, "writing reg: %x, value: 0x%.16llx, ptr: " PTR_FMT, offset, value, lapic_regs + offset);
83 *(volatile u32 *) (lapic_regs + offset + 0x10) = value >> 32;
84 *(volatile u32 *) (lapic_regs + offset) = value;
85}
86
87static void lapic_wait_sent(void)
88{
89 // Wait for the delivery status bit to be set
90 while (lapic_read32(APIC_INTERRUPT_COMMAND_REG_BEGIN) & BIT(12))
91 ;
92}
93
94void lapic_interrupt_full(u8 vec, u8 dest, lapic_delivery_mode_t delivery_mode, lapic_dest_mode_t dest_mode, bool level, bool trigger, lapic_shorthand_t shorthand)
95{
96 u64 value = 0;
97 value |= SET_BITS(0, 8, vec); // Interrupt Vector
98 value |= SET_BITS(8, 3, delivery_mode); // Delivery mode
99 value |= SET_BITS(11, 1, dest_mode); // Logical destination mode
100 value |= SET_BITS(12, 1, 0); // Delivery status (0)
101 value |= SET_BITS(14, 1, level); // Level
102 value |= SET_BITS(15, 1, trigger); // Trigger mode
103 value |= SET_BITS(18, 2, shorthand); // Destination shorthand
104 value |= SET_BITS(56, 8, (u64) dest); // Destination
105
106 lapic_write32(APIC_REG_ERROR_STATUS, value: 0);
107 lapic_write64(APIC_INTERRUPT_COMMAND_REG_BEGIN, value);
108 lapic_wait_sent();
109}
110
111void lapic_interrupt(u8 vec, u8 dest, lapic_delivery_mode_t delivery_mode, lapic_dest_mode_t dest_mode, lapic_shorthand_t shorthand)
112{
113 lapic_interrupt_full(vec, dest, delivery_mode, dest_mode, level: true, trigger: false, shorthand);
114}
115
116static void lapic_memory_setup(void)
117{
118 if (!cpu_has_feature(CPU_FEATURE_APIC))
119 mos_panic("APIC is not supported");
120
121 if (!cpu_has_feature(CPU_FEATURE_MSR))
122 mos_panic("MSR is not supported");
123
124 const ptr_t base_addr = x86_acpi_madt->lapic_addr;
125 pr_dinfo2(x86_lapic, "base address: " PTR_FMT, base_addr);
126
127 if (!pmm_find_reserved_region(needle: base_addr))
128 {
129 pr_info("LAPIC: reserve " PTR_FMT, base_addr);
130 pmm_reserve_address(base_addr);
131 }
132
133 lapic_regs = pa_va(base_addr);
134}
135
136void lapic_enable(void)
137{
138 if (once())
139 lapic_memory_setup();
140
141 // (https://wiki.osdev.org/APIC#Local_APIC_configuration)
142 // To enable the Local APIC to receive interrupts it is necessary to configure the "Spurious Interrupt Vector Register".
143 // The correct value for this field is
144 // - the IRQ number that you want to map the spurious interrupts to within the lowest 8 bits, and
145 // - the 8th bit set to 1
146 // to actually enable the APIC
147 const u32 spurious_intr_vec = lapic_read32(APIC_REG_SPURIOUS_INTR_VEC) | 0x100;
148 lapic_write32(APIC_REG_SPURIOUS_INTR_VEC, value: spurious_intr_vec);
149}
150
151void lapic_set_timer(u32 initial_count)
152{
153 lapic_write32(APIC_REG_TIMER_DIVIDE_CONFIG, value: 0x3); // divide by 16
154 lapic_write32(APIC_REG_LVT_TIMER, value: 0x20000 | 32); // periodic timer mode
155 lapic_write32(APIC_REG_TIMER_INITIAL_COUNT, value: initial_count); // start the timer
156}
157
158void lapic_eoi(void)
159{
160 lapic_write32(APIC_REG_EOI, value: 0);
161}
162