This repository was archived by the owner on Jan 4, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathAPIC.c
More file actions
314 lines (253 loc) · 10.3 KB
/
Copy pathAPIC.c
File metadata and controls
314 lines (253 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#include <APIC.h>
#include <../../include/Io.h>
#include <../../kernel/core/Kernel.h>
#include <../../kernel/etc/Console.h>
#include <../../mm/VMem.h>
#include <../sound/Generic.h>
#include <Panic.h>
#include <x64.h>
// --- Register Definitions ---
// Local APIC registers (offsets from LAPIC base)
#define LAPIC_ID 0x0020 // LAPIC ID
#define LAPIC_VER 0x0030 // LAPIC Version
#define LAPIC_TPR 0x0080 // Task Priority
#define LAPIC_EOI 0x00B0 // EOI
#define LAPIC_LDR 0x00D0 // Logical Destination
#define LAPIC_DFR 0x00E0 // Destination Format
#define LAPIC_SVR 0x00F0 // Spurious Interrupt Vector
#define LAPIC_ESR 0x0280 // Error Status
#define LAPIC_ICR_LOW 0x0300 // Interrupt Command Reg low
#define LAPIC_ICR_HIGH 0x0310 // Interrupt Command Reg high
#define LAPIC_LVT_TIMER 0x0320 // LVT Timer
#define LAPIC_LVT_LINT0 0x0350 // LVT LINT0
#define LAPIC_LVT_LINT1 0x0360 // LVT LINT1
#define LAPIC_LVT_ERROR 0x0370 // LVT Error
#define LAPIC_TIMER_INIT_COUNT 0x0380 // Initial Count (for Timer)
#define LAPIC_TIMER_CUR_COUNT 0x0390 // Current Count (for Timer)
#define LAPIC_TIMER_DIV 0x03E0 // Divide Configuration
// I/O APIC registers
#define IOAPIC_REG_ID 0x00 // ID Register
#define IOAPIC_REG_VER 0x01 // Version Register
#define IOAPIC_REG_TABLE 0x10 // Redirection Table
// --- Constants ---
#define APIC_BASE_MSR 0x1B
#define APIC_BASE_MSR_ENABLE 0x800
#define IOAPIC_DEFAULT_PHYS_ADDR 0xFEC00000
#define LAPIC_LVT_TIMER_SCALE_FACTOR 1
// --- Global Variables ---
// For now, a single global instance for BSP. Will be replaced by true per-CPU data in SMP.
static PerCpuData g_bsp_per_cpu_data = {
.lapic_base = NULL,
.apic_id = 0,
.apic_timer_freq_hz = 1000,
.apic_timer_ticks = 0,
.apic_bus_freq = 0,
.apic_calibrated = false,
};
PerCpuData* GetPerCpuData(void) {
return &g_bsp_per_cpu_data;
}
static volatile uint32_t* s_ioapic_base = NULL;
volatile uint32_t APIC_HZ = 250;
// --- Forward Declarations ---
static void lapic_write(uint32_t reg, uint32_t value);
static uint32_t lapic_read(uint32_t reg);
static void ioapic_write(uint8_t reg, uint32_t value);
static uint32_t ioapic_read(uint8_t reg);
static void ioapic_set_entry(uint8_t index, uint64_t data);
static bool detect_apic();
static bool setup_lapic();
static bool setup_ioapic();
#define PIC1_COMMAND 0x20
#define PIC1_DATA 0x21
#define PIC2_COMMAND 0xA0
#define PIC2_DATA 0xA1
static uint16_t s_irq_mask = 0xFFFF; // All masked initially
// Helper to write the cached mask to the PICs
static void pic_write_mask() {
outb(PIC1_DATA, s_irq_mask & 0xFF);
outb(PIC2_DATA, (s_irq_mask >> 8) & 0xFF);
}
void PICMaskAll() {
s_irq_mask = 0xFFFF;
pic_write_mask();
}
// --- MMIO Functions ---
static void lapic_write(uint32_t reg, uint32_t value) {
GetPerCpuData()->lapic_base[reg / 4] = value;
}
static uint32_t lapic_read(uint32_t reg) {
return GetPerCpuData()->lapic_base[reg / 4];
}
uint8_t lapic_get_id() {
// LAPIC_ID register: bits 24..31 hold the APIC ID in xAPIC mode
return (uint8_t)(lapic_read(LAPIC_ID) >> 24);
}
static void ioapic_write(uint8_t reg, uint32_t value) {
// I/O APIC uses an index/data pair for access
s_ioapic_base[0] = reg;
s_ioapic_base[4] = value;
}
static uint32_t ioapic_read(uint8_t reg) {
s_ioapic_base[0] = reg;
return s_ioapic_base[4];
}
// Sets a redirection table entry in the I/O APIC
static void ioapic_set_entry(uint8_t index, uint64_t data) {
ioapic_write(IOAPIC_REG_TABLE + index * 2, (uint32_t)data);
ioapic_write(IOAPIC_REG_TABLE + index * 2 + 1, (uint32_t)(data >> 32));
}
// --- Core APIC Functions ---
// Main entry point to initialize the APIC system
bool ApicInstall() {
if (!detect_apic()) {
PrintKernelError("APIC: No local APIC found or supported.\n");
return false;
}
PICMaskAll();
if (!setup_lapic()) {
PrintKernelError("APIC: Failed to setup Local APIC.\n");
return false;
}
if (!setup_ioapic()) {
PrintKernelError("APIC: Failed to setup I/O APIC.\n");
return false;
}
return true;
}
void ApicSendEoi() {
lapic_write(LAPIC_EOI, 0);
}
// --- I/O APIC Interrupt Management ---
void ApicEnableIrq(uint8_t irq_line) {
ASSERT(irq_line != 0 && irq_line != 2);
// IRQ line -> Vector 32 + IRQ
// For now, a simple 1:1 mapping for legacy IRQs 0-15
// Route to the current CPU's LAPIC ID (BSP), not hard-coded 0
uint8_t dest_apic_id = lapic_get_id();
uint64_t redirect_entry = (32 + irq_line); // Vector
redirect_entry |= (0b000ull << 8); // Delivery Mode: Fixed
redirect_entry |= (0b0ull << 11); // Destination Mode: Physical
redirect_entry |= (0b0ull << 13); // Trigger Mode: Edge
redirect_entry |= (0b0ull << 15); // Polarity: High (active high)
// Unmask (bit 16 = 0)
// Destination field (bits 56..63)
redirect_entry |= ((uint64_t)dest_apic_id << 56);
ioapic_set_entry(irq_line, redirect_entry);
}
void ApicDisableIrq(uint8_t irq_line) {
// To disable, we set the mask bit (bit 16)
uint64_t redirect_entry = (1 << 16);
ioapic_set_entry(irq_line, redirect_entry);
}
void ApicMaskAll() {
// Mask all 24 redirection entries in the I/O APIC
for (int i = 0; i < 24; i++) {
ApicDisableIrq(i);
}
}
// --- APIC Timer Management ---
void ApicTimerInstall(uint32_t frequency_hz) {
PerCpuData* cpu_data = GetPerCpuData();
cpu_data->apic_timer_freq_hz = frequency_hz;
// Calibrate and set the initial count
ApicTimerSetFrequency(cpu_data->apic_timer_freq_hz);
PrintKernelF("APIC: Timer installed at %d Hz.\n", frequency_hz);
}
void ApicTimerSetFrequency(uint32_t frequency_hz) {
if (frequency_hz == 0) return;
PerCpuData* cpu_data = GetPerCpuData();
cpu_data->apic_timer_freq_hz = frequency_hz;
APIC_HZ = frequency_hz; // Global for now, but should be per-CPU
// Only calibrate once to avoid expensive operations
if (!cpu_data->apic_calibrated) {
// Set divider first
lapic_write(LAPIC_TIMER_DIV, 0xB); // Divide by 1 (no division)
// Configure PIT for calibration
outb(PIT_COMMAND, 0xB6);
uint16_t divisor = 11932; // 100Hz
outb(PIT_CHANNEL_2, divisor & 0xFF);
outb(PIT_CHANNEL_2, (divisor >> 8) & 0xFF);
uint8_t speaker_reg = inb(PC_SPEAKER_PORT);
outb(PC_SPEAKER_PORT, speaker_reg | 0x01);
// One-shot calibration
lapic_write(LAPIC_LVT_TIMER, 32 | (0b00 << 17));
lapic_write(LAPIC_TIMER_INIT_COUNT, 0xFFFFFFFF);
// Wait with timeout
uint32_t timeout = 100000;
while ((inb(PC_SPEAKER_PORT) & 0x20) != 0 && --timeout);
while ((inb(PC_SPEAKER_PORT) & 0x20) == 0 && --timeout);
uint32_t start_count = lapic_read(LAPIC_TIMER_CUR_COUNT);
timeout = 100000;
while ((inb(PC_SPEAKER_PORT) & 0x20) != 0 && --timeout);
while ((inb(PC_SPEAKER_PORT) & 0x20) == 0 && --timeout);
uint32_t end_count = lapic_read(LAPIC_TIMER_CUR_COUNT);
outb(PC_SPEAKER_PORT, speaker_reg);
lapic_write(LAPIC_LVT_TIMER, 1 << 16);
if (timeout > 0) {
uint32_t ticks_per_10ms = start_count - end_count;
cpu_data->apic_bus_freq = ticks_per_10ms * 100;
cpu_data->apic_calibrated = true;
} else {
cpu_data->apic_bus_freq = 100000000; // Fallback: 100MHz
PrintKernelWarning("APIC: Calibration timeout, using fallback frequency\n");
}
}
uint32_t initial_count = cpu_data->apic_bus_freq / frequency_hz;
lapic_write(LAPIC_LVT_TIMER, 32 | (0b01 << 17)); // Periodic mode
lapic_write(LAPIC_TIMER_INIT_COUNT, initial_count);
}
// --- Private Setup Functions ---
// Check for APIC presence via CPUID
static bool detect_apic() {
uint32_t eax, ebx, ecx, edx;
cpuid(1, &eax, &ebx, &ecx, &edx);
return (edx & (1 << 9)) != 0; // Check for APIC feature bit
}
// Initialize the Local APIC
static bool setup_lapic() {
// Get LAPIC physical base address from MSR
uint64_t lapic_base_msr = rdmsr(APIC_BASE_MSR);
uint64_t lapic_phys_base = lapic_base_msr & 0xFFFFFFFFFFFFF000ULL;
// Map the LAPIC into virtual memory
PerCpuData* cpu_data = GetPerCpuData();
cpu_data->lapic_base = (volatile uint32_t*)VMemAlloc(PAGE_SIZE);
if (!cpu_data->lapic_base) {
PrintKernelError("APIC: Failed to allocate virtual memory for LAPIC.\n");
return false;
}
if (VMemUnmap((uint64_t)cpu_data->lapic_base, PAGE_SIZE) != VMEM_SUCCESS) {
PrintKernelError("APIC: Failed to unmap LAPIC MMIO.\n");
return false;
}
if (VMemMapMMIO((uint64_t)cpu_data->lapic_base, lapic_phys_base, PAGE_SIZE, PAGE_WRITABLE | PAGE_NOCACHE) != VMEM_SUCCESS) {
PrintKernelError("APIC: Failed to map LAPIC MMIO.\n");
return false;
}
// Enable the LAPIC by setting the enable bit in the MSR and the spurious vector register
wrmsr(APIC_BASE_MSR, lapic_base_msr | APIC_BASE_MSR_ENABLE);
lapic_write(LAPIC_SVR, 0x1FF);
// Set TPR to 0 to accept all interrupts
lapic_write(LAPIC_TPR, 0);
return true;
}
// Initialize the I/O APIC
static bool setup_ioapic() {
// Map the I/O APIC into virtual memory. We assume the standard physical address.
s_ioapic_base = (volatile uint32_t*)VMemAlloc(PAGE_SIZE);
if (!s_ioapic_base) {
PrintKernelError("APIC: Failed to allocate virtual memory for I/O APIC.\n");
return false;
}
VMemUnmap((uint64_t)s_ioapic_base, PAGE_SIZE);
if (VMemMapMMIO((uint64_t)s_ioapic_base, IOAPIC_DEFAULT_PHYS_ADDR, PAGE_SIZE, PAGE_WRITABLE | PAGE_NOCACHE) != VMEM_SUCCESS) {
PrintKernelError("APIC: Failed to map I/O APIC MMIO.\n");
return false;
}
// Read the I/O APIC version to verify it's working
uint32_t version_reg = ioapic_read(IOAPIC_REG_VER);
uint8_t max_redirects = (version_reg >> 16) & 0xFF;
// Mask all interrupts initially
ApicMaskAll();
return true;
}