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