140 lines
4.4 KiB
C
140 lines
4.4 KiB
C
#include <zinnia/alloc.h>
|
|
#include <zinnia/assert.h>
|
|
#include <zinnia/clock.h>
|
|
#include <zinnia/mmio.h>
|
|
#include <zinnia/percpu.h>
|
|
#include <zinnia/pmap.h>
|
|
#include <zinnia/vma.h>
|
|
#include <stdint.h>
|
|
#include <x86_64/apic.h>
|
|
#include <x86_64/asm.h>
|
|
#include <x86_64/defs.h>
|
|
|
|
static uint64_t lapic_read_reg(struct local_apic* lapic, uint32_t reg) {
|
|
if (lapic->xapic_regs) {
|
|
if (reg == APIC_REG_ICR) {
|
|
uint64_t lo, hi;
|
|
lo = mmio_read32(lapic->xapic_regs + APIC_REG_ICR);
|
|
hi = mmio_read32(lapic->xapic_regs + APIC_REG_ICR_HI);
|
|
return (hi << 32) | lo;
|
|
} else {
|
|
return mmio_read32(lapic->xapic_regs + reg);
|
|
}
|
|
} else {
|
|
return asm_rdmsr(0x800 + (reg >> 4));
|
|
}
|
|
}
|
|
|
|
static void lapic_write_reg(struct local_apic* lapic, uint32_t reg, uint64_t value) {
|
|
if (lapic->xapic_regs) {
|
|
if (reg == APIC_REG_ICR) {
|
|
mmio_write32(lapic->xapic_regs + APIC_REG_ICR_HI, value >> 32);
|
|
mmio_write32(lapic->xapic_regs + APIC_REG_ICR, (uint32_t)value);
|
|
} else {
|
|
mmio_write32(lapic->xapic_regs + reg, (uint32_t)value);
|
|
}
|
|
} else {
|
|
asm_wrmsr(0x800 + (reg >> 4), value);
|
|
}
|
|
}
|
|
|
|
void lapic_init(struct local_apic* lapic) {
|
|
uint64_t apic_msr = asm_rdmsr(0x1B);
|
|
apic_msr |= 1 << 11; // Enable APIC.
|
|
|
|
struct cpuid cpuid = asm_cpuid(1, 0);
|
|
if (cpuid.ecx & CPUID_1C_X2APIC)
|
|
apic_msr |= 1 << 10;
|
|
else {
|
|
uintptr_t vaddr = vm_alloc(0x1000);
|
|
pmap_map(&kernel_vma.pmap, vaddr, apic_msr & 0xFFFF'F000, PTE_READ | PTE_WRITE, CACHE_MMIO);
|
|
lapic->xapic_regs = (void*)vaddr;
|
|
assert(lapic->xapic_regs, "Failed to allocate virtual memory!\n");
|
|
}
|
|
|
|
asm_wrmsr(0x1B, apic_msr);
|
|
|
|
lapic->id = lapic_read_reg(lapic, APIC_REG_ID);
|
|
if (lapic->xapic_regs)
|
|
lapic->id >>= 24;
|
|
|
|
// Reset the TPR.
|
|
lapic_write_reg(lapic, APIC_REG_TPR, 0);
|
|
// Enable APIC bit in the SIVR.
|
|
lapic_write_reg(lapic, APIC_REG_SIVR, (lapic_read_reg(lapic, APIC_REG_SIVR) & ~0xFF) | 0x100 | IDT_SPURIOUS);
|
|
|
|
if (lapic->xapic_regs) {
|
|
lapic_write_reg(lapic, APIC_REG_DFR, 0xF000'0000);
|
|
// Logical destination = LAPIC ID.
|
|
lapic_write_reg(lapic, APIC_REG_LDR, lapic_read_reg(lapic, APIC_REG_ID));
|
|
}
|
|
|
|
// TODO: Parse MADT and setup NMI sources.
|
|
|
|
// Tell the APIC timer to divide by 16.
|
|
lapic_write_reg(lapic, APIC_REG_DCR, 3);
|
|
// Set the timer to the highest value possible.
|
|
lapic_write_reg(lapic, APIC_REG_ICR_TIMER, 0xFFFF'FFFF);
|
|
|
|
// Sleep for 10 milliseconds.
|
|
clock_spin_ns(10'000'000);
|
|
|
|
lapic->ticks_per_10ms = 0xFFFF'FFFF - lapic_read_reg(lapic, APIC_REG_CCR);
|
|
lapic_write_reg(lapic, APIC_REG_LVT_TR, IDT_IPI_RESCHED | 0x20000);
|
|
lapic_write_reg(lapic, APIC_REG_ICR_TIMER, lapic->ticks_per_10ms);
|
|
}
|
|
|
|
void lapic_eoi(struct local_apic* lapic) {
|
|
lapic_write_reg(lapic, APIC_REG_EOI, 0);
|
|
}
|
|
|
|
void lapic_send_ipi(
|
|
struct local_apic* lapic,
|
|
uint32_t target,
|
|
uint8_t vector,
|
|
enum apic_delivery_mode delivery_mode,
|
|
enum apic_dest_mode dest_mode,
|
|
enum apic_level level,
|
|
enum apic_trigger_mode trigger_mode
|
|
) {
|
|
uint64_t icr = vector;
|
|
icr |= (uint64_t)delivery_mode << 8;
|
|
icr |= (uint64_t)dest_mode << 11;
|
|
icr |= (uint64_t)level << 14;
|
|
icr |= (uint64_t)trigger_mode << 15;
|
|
|
|
if (lapic->xapic_regs)
|
|
icr |= (uint64_t)(uint8_t)target << 56;
|
|
else
|
|
icr |= (uint64_t)target << 32;
|
|
|
|
lapic_write_reg(lapic, APIC_REG_ICR, icr);
|
|
|
|
if (lapic->xapic_regs) {
|
|
while (lapic_read_reg(lapic, APIC_REG_ICR) & (APIC_DELIVERY_STATUS_PENDING << 12))
|
|
asm volatile("pause");
|
|
}
|
|
}
|
|
|
|
constexpr uint16_t PIC1_COMMAND_PORT = 0x20;
|
|
constexpr uint16_t PIC1_DATA_PORT = 0x21;
|
|
constexpr uint16_t PIC2_COMMAND_PORT = 0xA0;
|
|
constexpr uint16_t PIC2_DATA_PORT = 0xA1;
|
|
|
|
void pic_disable() {
|
|
// We initialize the PIC properly, but completely disable it and use the APIC in favor of it.
|
|
// Remap IRQs so they start at 0x20 since interrupts 0x00..0x1F are used by CPU exceptions.
|
|
asm_outb(PIC1_COMMAND_PORT, 0x11);
|
|
asm_outb(PIC1_DATA_PORT, 0x20);
|
|
asm_outb(PIC1_DATA_PORT, 0x04);
|
|
asm_outb(PIC1_DATA_PORT, 0x01);
|
|
asm_outb(PIC1_DATA_PORT, 0xFF);
|
|
|
|
// Same for the slave PIC.
|
|
asm_outb(PIC2_COMMAND_PORT, 0x11);
|
|
asm_outb(PIC2_DATA_PORT, 0x28);
|
|
asm_outb(PIC2_DATA_PORT, 0x02);
|
|
asm_outb(PIC2_DATA_PORT, 0x01);
|
|
asm_outb(PIC2_DATA_PORT, 0xFF);
|
|
}
|