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

4.4 KiB
Raw Blame History

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

Инициализация

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.

Доступ к регистрам

#[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() — конец прерывания

pub fn send_eoi() {
    write_lapic_reg(LAPIC_EOI, 0);  // 0x0B0
}

broadcast_ipi_exclude_self(vector) — IPI всем, кроме себя

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() — определение текущего ядра

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 доставляет прерывание. Запись блокирующая (ждёт готовности шины).