Files
Elyz/kernel/docs/cpu/lapic.md
2026-07-07 16:40:41 +03:00

116 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Local APIC: `lapic.rs`
## Аппаратная модель
**Local APIC** (Advanced Programmable Interrupt Controller) — это
встроенный в каждое ядро x86-64 контроллер прерываний.
```
Local APIC (MMIO, начиная с 0xFEE00_000)
┌──────────────────────────────┐
│ 0x020: IRR (In-Service Reg) │
│ 0x030: TMR (Trigger Mode) │
│ 0x080: EOI │ ← запись 0 = подтверждение прерывания
│ 0x0B0: LINT0/LINT1 │
│ 0x0E0: Timer │
│ 0x200: LVT Error │
│ 0x300: ICR (Interrupt Cmd) │ ← отправка межпроцессорного прерывания
│ 0x310: ICR_HIGH (APIC ID) │
└──────────────────────────────┘
```
## Регистры
| Смещение | Регистр | Назначение |
|----------|---------|------------|
| 0x0B0 | EOI | End Of Interrupt — подтверждение обработки |
| 0x300 | ICR (low) | Interrupt Command Register — отправка IPI |
| 0x310 | ICR (high) | Destination APIC ID |
## Инициализация
```rust
static LAPIC_VIRT_BASE: AtomicU64 = AtomicU64::new(0);
pub fn init() {
// LAPIC отображён bootloader'ом в HHDM по адресу 0xFEE00_000
LAPIC_VIRT_BASE.store(LAPIC_DEFAULT_BASE + get_hhdm(), Ordering::SeqCst);
}
```
LAPIC расположен по физическому адресу `0xFEE0_0000`. Ядро получает
виртуальный базовый адрес, добавляя HHDM offset.
## Доступ к регистрам
```rust
#[inline(always)]
fn write_lapic_reg(offset: u64, value: u32) {
let base = LAPIC_VIRT_BASE.load(Ordering::Relaxed);
if base == 0 { return; } // LAPIC ещё не инициализирован
unsafe { core::ptr::write_volatile((base + offset) as *mut u32, value) }
}
```
- `write_volatile` — запрещает компилятору оптимизировать обращение
(регистры MMIO).
- `Relaxed` ordering — для отладки/инициализации достаточно.
## Операции
### send_eoi() — конец прерывания
```rust
pub fn send_eoi() {
write_lapic_reg(LAPIC_EOI, 0); // 0x0B0
}
```
### broadcast_ipi_exclude_self(vector) — IPI всем, кроме себя
```rust
pub fn broadcast_ipi_exclude_self(vector: u8) {
// ICR[19:18] = 10b (All Excluding Self)
// ICR[14] = 1 (Assert)
// ICR[7:0] = vector
let icr_low = (2 << 18) | (1 << 14) | (vector as u32);
write_lapic_reg(LAPIC_ICR_LOW, icr_low);
}
```
Используется для TLB shootdown — необходимо разослать всем ядрам
(кроме текущего) IPI с вектором `TLB_SHOOTDOWN_VECTOR`.
### current_core_id() — определение текущего ядра
```rust
pub fn current_core_id() -> u32 {
// CPUID leaf 1: EBX[31:24] = Local APIC ID
let mut ebx: u32;
asm!(
"mov {tmp:r}, rbx", // спрятать rbx (резерв LLVM)
"mov eax, 1", "cpuid",
"mov {out:e}, ebx", // сохранить EBX
"mov rbx, {tmp:r}", // восстановить rbx
tmp = out(reg) _,
out = out(reg) ebx,
...
);
ebx >> 24
}
```
**RBX проблема:** LLVM резервирует RBX, поэтому его нужно сохранять
и восстанавливать вручную вокруг CPUID инструкции.
## Тонкости
1. **MMIO vs MSR**: LAPIC можно программировать через MSR (IA32_APIC_BASE)
и через MMIO. Bootloader (Limine) настраивает MMIO mapping в HHDM.
2. **Инициализация**: LAPIC уже включён bootloader'ом. `init()` просто
сохраняет виртуальный адрес.
3. **x2APIC**: не используется (в текущей версии — MMIO xAPIC).
4. **EOI**: обязателен после каждого прерывания от LAPIC (включая IPI).
5. **ICR запись**: после записи в ICR Low, шина APIC доставляет
прерывание. Запись блокирующая (ждёт готовности шины).