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