4.4 KiB
4.4 KiB
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).Relaxedordering — для отладки/инициализации достаточно.
Операции
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 инструкции.
Тонкости
- MMIO vs MSR: LAPIC можно программировать через MSR (IA32_APIC_BASE) и через MMIO. Bootloader (Limine) настраивает MMIO mapping в HHDM.
- Инициализация: LAPIC уже включён bootloader'ом.
init()просто сохраняет виртуальный адрес. - x2APIC: не используется (в текущей версии — MMIO xAPIC).
- EOI: обязателен после каждого прерывания от LAPIC (включая IPI).
- ICR запись: после записи в ICR Low, шина APIC доставляет прерывание. Запись блокирующая (ждёт готовности шины).