From: Tulio A M Mendes Date: Tue, 10 Feb 2026 08:40:28 +0000 (-0300) Subject: feat: Fase 6 — LAPIC + IOAPIC drivers, replace legacy PIC 8259 X-Git-Url: https://projects.tadryanom.me/docs/POSIX_ROADMAP.md?a=commitdiff_plain;h=a57918c4c2e4a7f28324cfdb30055ad0af208110;p=AdrOS.git feat: Fase 6 — LAPIC + IOAPIC drivers, replace legacy PIC 8259 New files: - include/arch/x86/lapic.h — LAPIC register definitions and API - include/arch/x86/ioapic.h — IOAPIC register definitions and API - src/arch/x86/lapic.c — Local APIC driver: init, EOI, MMIO access, timer calibration via PIT channel 2, pic_disable() - src/arch/x86/ioapic.c — I/O APIC driver: init, IRQ routing, mask/unmask per-IRQ line Changes: - vmm.h: Add VMM_FLAG_PWT, VMM_FLAG_PCD, VMM_FLAG_NOCACHE for MMIO - vmm.c: Translate PWT/PCD flags to x86 PTE bits in vmm_flags_to_x86 - arch_platform.c: Init LAPIC+IOAPIC after syscall_init, route ISA IRQs (timer=32, kbd=33, ATA=46) through IOAPIC, disable PIC only after IOAPIC routes are live - idt.c: Send EOI BEFORE handler callback (critical: schedule() in timer handler context-switches away; deferred EOI blocks LAPIC). Add IDT gate for spurious vector 255; skip EOI for spurious interrupts per Intel spec. - interrupts.S: Add ISR stub for vector 255 (LAPIC spurious) - timer.c: Use LAPIC periodic timer when available, fallback to PIT Key design decisions: - LAPIC MMIO mapped at 0xC0200000 (above kernel _end, below heap) - IOAPIC MMIO mapped at 0xC0201000 - Both mapped with PCD+PWT (cache-disable) to prevent MMIO caching - PIC disabled only AFTER IOAPIC routes configured (avoids IRQ gap) - EOI sent before handler to prevent LAPIC starvation on context switch - Spurious vector 255 has IDT entry but no EOI (Intel requirement) - LAPIC timer calibrated against PIT channel 2 (~10ms measurement) Bugs fixed during development: - VA 0xC0100000 overlapped kernel text — moved to 0xC0200000 - pic_disable() inside lapic_init() caused IRQ gap — moved to caller - EOI after handler blocked LAPIC when schedule() context-switched - Missing IDT entry for vector 255 caused triple fault on spurious IRQ Passes: make, cppcheck, QEMU smoke test (all init tests OK). --- diff --git a/include/arch/x86/acpi.h b/include/arch/x86/acpi.h new file mode 100644 index 0000000..d3b6288 --- /dev/null +++ b/include/arch/x86/acpi.h @@ -0,0 +1,105 @@ +#ifndef ARCH_X86_ACPI_H +#define ARCH_X86_ACPI_H + +#include + +/* RSDP (Root System Description Pointer) — ACPI 1.0 */ +struct acpi_rsdp { + char signature[8]; /* "RSD PTR " */ + uint8_t checksum; + char oem_id[6]; + uint8_t revision; /* 0 = ACPI 1.0, 2 = ACPI 2.0+ */ + uint32_t rsdt_address; +} __attribute__((packed)); + +/* SDT header — common to all ACPI tables */ +struct acpi_sdt_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + uint32_t creator_id; + uint32_t creator_revision; +} __attribute__((packed)); + +/* RSDT (Root System Description Table) */ +struct acpi_rsdt { + struct acpi_sdt_header header; + uint32_t entries[]; /* array of 32-bit physical pointers to other SDTs */ +} __attribute__((packed)); + +/* MADT (Multiple APIC Description Table) */ +struct acpi_madt { + struct acpi_sdt_header header; + uint32_t lapic_address; /* Physical address of LAPIC */ + uint32_t flags; /* bit 0: dual 8259 PICs installed */ +} __attribute__((packed)); + +/* MADT entry header */ +struct madt_entry_header { + uint8_t type; + uint8_t length; +} __attribute__((packed)); + +/* MADT entry types */ +#define MADT_TYPE_LAPIC 0 +#define MADT_TYPE_IOAPIC 1 +#define MADT_TYPE_ISO 2 /* Interrupt Source Override */ +#define MADT_TYPE_NMI_SOURCE 3 +#define MADT_TYPE_LAPIC_NMI 4 + +/* MADT: Processor Local APIC */ +struct madt_lapic { + struct madt_entry_header header; + uint8_t acpi_processor_id; + uint8_t apic_id; + uint32_t flags; /* bit 0: processor enabled */ +} __attribute__((packed)); + +#define MADT_LAPIC_ENABLED (1U << 0) + +/* MADT: I/O APIC */ +struct madt_ioapic { + struct madt_entry_header header; + uint8_t ioapic_id; + uint8_t reserved; + uint32_t ioapic_address; + uint32_t gsi_base; /* Global System Interrupt base */ +} __attribute__((packed)); + +/* MADT: Interrupt Source Override */ +struct madt_iso { + struct madt_entry_header header; + uint8_t bus_source; /* always 0 (ISA) */ + uint8_t irq_source; /* ISA IRQ number */ + uint32_t gsi; /* Global System Interrupt */ + uint16_t flags; /* polarity + trigger mode */ +} __attribute__((packed)); + +/* Maximum CPUs we support */ +#define ACPI_MAX_CPUS 16 + +/* Parsed ACPI info */ +struct acpi_info { + uint8_t num_cpus; + uint8_t bsp_id; /* BSP LAPIC ID */ + uint8_t cpu_lapic_ids[ACPI_MAX_CPUS]; /* LAPIC IDs of all CPUs */ + uint8_t cpu_enabled[ACPI_MAX_CPUS]; /* 1 if CPU is enabled */ + + uint32_t ioapic_address; /* Physical address of IOAPIC */ + uint8_t ioapic_id; + uint32_t ioapic_gsi_base; + + uint32_t lapic_address; /* Physical address of LAPIC (from MADT) */ +}; + +/* Find and parse ACPI tables. Returns 0 on success, -1 on failure. */ +int acpi_init(void); + +/* Get parsed ACPI info. Valid only after acpi_init() succeeds. */ +const struct acpi_info* acpi_get_info(void); + +#endif diff --git a/include/arch/x86/ioapic.h b/include/arch/x86/ioapic.h new file mode 100644 index 0000000..e741d47 --- /dev/null +++ b/include/arch/x86/ioapic.h @@ -0,0 +1,46 @@ +#ifndef ARCH_X86_IOAPIC_H +#define ARCH_X86_IOAPIC_H + +#include + +/* IOAPIC register select / data window (MMIO) */ +#define IOAPIC_REGSEL 0x00 +#define IOAPIC_REGWIN 0x10 + +/* IOAPIC registers (via REGSEL) */ +#define IOAPIC_REG_ID 0x00 +#define IOAPIC_REG_VER 0x01 +#define IOAPIC_REG_ARB 0x02 +#define IOAPIC_REG_REDTBL 0x10 /* base; entry N = 0x10 + 2*N (lo), 0x11 + 2*N (hi) */ + +/* Redirection entry bits */ +#define IOAPIC_RED_MASKED (1U << 16) +#define IOAPIC_RED_LEVEL (1U << 15) +#define IOAPIC_RED_ACTIVELO (1U << 13) +#define IOAPIC_RED_LOGICAL (1U << 11) + +/* Default IOAPIC base address (can be overridden by ACPI MADT) */ +#define IOAPIC_DEFAULT_BASE 0xFEC00000U + +/* Maximum IRQ inputs on a standard IOAPIC */ +#define IOAPIC_MAX_IRQS 24 + +/* Initialize the IOAPIC. Returns 1 on success, 0 if not available. */ +int ioapic_init(void); + +/* Route an ISA IRQ to a specific IDT vector, targeting a specific LAPIC ID. + * irq: ISA IRQ number (0-15 typically) + * vector: IDT vector number (32-255) + * lapic_id: destination LAPIC ID */ +void ioapic_route_irq(uint8_t irq, uint8_t vector, uint8_t lapic_id); + +/* Mask (disable) an IRQ line on the IOAPIC. */ +void ioapic_mask_irq(uint8_t irq); + +/* Unmask (enable) an IRQ line on the IOAPIC. */ +void ioapic_unmask_irq(uint8_t irq); + +/* Returns 1 if IOAPIC is enabled and active. */ +int ioapic_is_enabled(void); + +#endif diff --git a/include/arch/x86/lapic.h b/include/arch/x86/lapic.h new file mode 100644 index 0000000..f841d1f --- /dev/null +++ b/include/arch/x86/lapic.h @@ -0,0 +1,68 @@ +#ifndef ARCH_X86_LAPIC_H +#define ARCH_X86_LAPIC_H + +#include + +/* LAPIC register offsets (from LAPIC base) */ +#define LAPIC_ID 0x020 +#define LAPIC_VERSION 0x030 +#define LAPIC_TPR 0x080 /* Task Priority Register */ +#define LAPIC_EOI 0x0B0 /* End of Interrupt */ +#define LAPIC_SVR 0x0F0 /* Spurious Interrupt Vector Register */ +#define LAPIC_ESR 0x280 /* Error Status Register */ +#define LAPIC_ICR_LO 0x300 /* Interrupt Command Register (low) */ +#define LAPIC_ICR_HI 0x310 /* Interrupt Command Register (high) */ +#define LAPIC_TIMER_LVT 0x320 /* LVT Timer Register */ +#define LAPIC_LINT0_LVT 0x350 /* LVT LINT0 */ +#define LAPIC_LINT1_LVT 0x360 /* LVT LINT1 */ +#define LAPIC_TIMER_ICR 0x380 /* Timer Initial Count Register */ +#define LAPIC_TIMER_CCR 0x390 /* Timer Current Count Register */ +#define LAPIC_TIMER_DCR 0x3E0 /* Timer Divide Configuration Register */ + +/* SVR bits */ +#define LAPIC_SVR_ENABLE 0x100 /* APIC Software Enable */ +#define LAPIC_SVR_VECTOR 0xFF /* Spurious vector number */ + +/* LVT Timer modes */ +#define LAPIC_TIMER_ONESHOT 0x00000000 +#define LAPIC_TIMER_PERIODIC 0x00020000 +#define LAPIC_LVT_MASKED 0x00010000 + +/* Timer divide values for DCR */ +#define LAPIC_TIMER_DIV_16 0x03 + +/* Spurious vector — pick an unused IDT slot */ +#define LAPIC_SPURIOUS_VEC 0xFF + +/* LAPIC timer IRQ vector — we use IDT slot 32 (same as PIT was) */ +#define LAPIC_TIMER_VEC 32 + +/* Initialize the Local APIC. Returns 1 if APIC enabled, 0 if not available. */ +int lapic_init(void); + +/* Send End-of-Interrupt to the LAPIC. Must be called at end of every LAPIC interrupt. */ +void lapic_eoi(void); + +/* Read LAPIC register */ +uint32_t lapic_read(uint32_t reg); + +/* Write LAPIC register */ +void lapic_write(uint32_t reg, uint32_t val); + +/* Get the LAPIC ID of the current CPU */ +uint32_t lapic_get_id(void); + +/* Start the LAPIC timer at the given frequency (approximate). */ +void lapic_timer_start(uint32_t frequency_hz); + +/* Stop the LAPIC timer. */ +void lapic_timer_stop(void); + +/* Returns 1 if LAPIC is enabled and active. */ +int lapic_is_enabled(void); + +/* Disable the legacy 8259 PIC by masking all IRQ lines. + * Call AFTER IOAPIC is fully configured with IRQ routes. */ +void pic_disable(void); + +#endif diff --git a/include/vmm.h b/include/vmm.h index 3ef6560..a7efbbb 100644 --- a/include/vmm.h +++ b/include/vmm.h @@ -7,6 +7,9 @@ #define VMM_FLAG_PRESENT (1 << 0) #define VMM_FLAG_RW (1 << 1) #define VMM_FLAG_USER (1 << 2) +#define VMM_FLAG_PWT (1 << 3) /* Page Write-Through */ +#define VMM_FLAG_PCD (1 << 4) /* Page Cache Disable */ +#define VMM_FLAG_NOCACHE (VMM_FLAG_PWT | VMM_FLAG_PCD) /* For MMIO regions */ #define VMM_FLAG_COW (1 << 9) /* OS-available bit: Copy-on-Write marker */ /* diff --git a/src/arch/x86/acpi.c b/src/arch/x86/acpi.c new file mode 100644 index 0000000..f6704b1 --- /dev/null +++ b/src/arch/x86/acpi.c @@ -0,0 +1,279 @@ +#include "arch/x86/acpi.h" +#include "uart_console.h" +#include "utils.h" +#include "vmm.h" + +#include +#include + +static struct acpi_info g_acpi_info; +static int g_acpi_valid = 0; + +/* The first 16MB is identity-mapped during early boot (boot.S maps 0-16MB). + * For addresses < 16MB we can use phys + 0xC0000000. + * For addresses >= 16MB we must temporarily map them via VMM. */ +#define KERNEL_VIRT_BASE 0xC0000000U +#define IDENTITY_LIMIT 0x01000000U /* 16MB */ + +/* Temporary VA window for mapping ACPI tables above the identity-mapped range. + * We use up to 16 pages (64KB) starting at a fixed VA above LAPIC/IOAPIC. */ +#define ACPI_TMP_VA_BASE 0xC0202000U +#define ACPI_TMP_VA_PAGES 16 +static uint32_t acpi_tmp_mapped = 0; /* bitmask of which pages are mapped */ + +/* Map a physical address and return a usable virtual pointer. + * For addresses in the identity-mapped range, just add KERNEL_VIRT_BASE. + * For others, temporarily map via VMM. */ +static const void* acpi_map_phys(uintptr_t phys, size_t len) { + if (phys + len <= IDENTITY_LIMIT) { + return (const void*)(phys + KERNEL_VIRT_BASE); + } + + /* Map all pages covering [phys, phys+len) into the temp VA window */ + uintptr_t page_start = phys & ~0xFFFU; + uintptr_t page_end = (phys + len + 0xFFF) & ~0xFFFU; + uint32_t num_pages = (uint32_t)((page_end - page_start) >> 12); + + if (num_pages > ACPI_TMP_VA_PAGES) { + uart_print("[ACPI] Table too large to map.\n"); + return NULL; + } + + for (uint32_t i = 0; i < num_pages; i++) { + uintptr_t va = ACPI_TMP_VA_BASE + i * 0x1000; + uintptr_t pa = page_start + i * 0x1000; + vmm_map_page((uint64_t)pa, (uint64_t)va, + VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE); + acpi_tmp_mapped |= (1U << i); + } + + uintptr_t offset = phys - page_start; + return (const void*)(ACPI_TMP_VA_BASE + offset); +} + +/* Unmap all temporarily mapped ACPI pages */ +static void acpi_unmap_all(void) { + for (uint32_t i = 0; i < ACPI_TMP_VA_PAGES; i++) { + if (acpi_tmp_mapped & (1U << i)) { + vmm_unmap_page((uint64_t)(ACPI_TMP_VA_BASE + i * 0x1000)); + } + } + acpi_tmp_mapped = 0; +} + +static int acpi_checksum(const void* ptr, size_t len) { + const uint8_t* p = (const uint8_t*)ptr; + uint8_t sum = 0; + for (size_t i = 0; i < len; i++) sum += p[i]; + return sum == 0; +} + +/* Search for "RSD PTR " signature in a memory range (physical addresses) */ +static const struct acpi_rsdp* find_rsdp_in_range(uintptr_t phys_start, uintptr_t phys_end) { + /* RSDP is always 16-byte aligned */ + for (uintptr_t addr = phys_start; addr < phys_end; addr += 16) { + const char* p = (const char*)(addr + KERNEL_VIRT_BASE); + if (p[0] == 'R' && p[1] == 'S' && p[2] == 'D' && p[3] == ' ' && + p[4] == 'P' && p[5] == 'T' && p[6] == 'R' && p[7] == ' ') { + const struct acpi_rsdp* rsdp = (const struct acpi_rsdp*)p; + if (acpi_checksum(rsdp, 20)) { + return rsdp; + } + } + } + return NULL; +} + +static const struct acpi_rsdp* find_rsdp(void) { + /* 1. Search EBDA (Extended BIOS Data Area) — first KB pointed to by BDA[0x40E] */ + uint16_t ebda_seg = *(const uint16_t*)(0x040E + KERNEL_VIRT_BASE); + uintptr_t ebda_phys = (uintptr_t)ebda_seg << 4; + if (ebda_phys >= 0x80000 && ebda_phys < 0xA0000) { + const struct acpi_rsdp* r = find_rsdp_in_range(ebda_phys, ebda_phys + 1024); + if (r) return r; + } + + /* 2. Search BIOS ROM area: 0xE0000 - 0xFFFFF */ + return find_rsdp_in_range(0xE0000, 0x100000); +} + +static int parse_madt(const struct acpi_madt* madt) { + g_acpi_info.lapic_address = madt->lapic_address; + + const uint8_t* ptr = (const uint8_t*)madt + sizeof(struct acpi_madt); + const uint8_t* end = (const uint8_t*)madt + madt->header.length; + + while (ptr + 2 <= end) { + const struct madt_entry_header* eh = (const struct madt_entry_header*)ptr; + if (eh->length < 2) break; + if (ptr + eh->length > end) break; + + switch (eh->type) { + case MADT_TYPE_LAPIC: { + const struct madt_lapic* lapic = (const struct madt_lapic*)ptr; + if (g_acpi_info.num_cpus < ACPI_MAX_CPUS) { + uint8_t idx = g_acpi_info.num_cpus; + g_acpi_info.cpu_lapic_ids[idx] = lapic->apic_id; + g_acpi_info.cpu_enabled[idx] = (lapic->flags & MADT_LAPIC_ENABLED) ? 1 : 0; + g_acpi_info.num_cpus++; + } + break; + } + case MADT_TYPE_IOAPIC: { + const struct madt_ioapic* ioapic = (const struct madt_ioapic*)ptr; + /* Use the first IOAPIC found */ + if (g_acpi_info.ioapic_address == 0) { + g_acpi_info.ioapic_address = ioapic->ioapic_address; + g_acpi_info.ioapic_id = ioapic->ioapic_id; + g_acpi_info.ioapic_gsi_base = ioapic->gsi_base; + } + break; + } + case MADT_TYPE_ISO: { + /* TODO: store interrupt source overrides for IRQ remapping */ + break; + } + default: + break; + } + + ptr += eh->length; + } + + return 0; +} + +int acpi_init(void) { + memset(&g_acpi_info, 0, sizeof(g_acpi_info)); + + const struct acpi_rsdp* rsdp = find_rsdp(); + if (!rsdp) { + uart_print("[ACPI] RSDP not found.\n"); + return -1; + } + + uart_print("[ACPI] RSDP found, revision="); + char tmp[12]; + itoa(rsdp->revision, tmp, 10); + uart_print(tmp); + uart_print("\n"); + + /* Get RSDT (ACPI 1.0 — 32-bit pointers). + * The RSDT may be above the 16MB identity-mapped range, so use acpi_map_phys. */ + uintptr_t rsdt_phys = rsdp->rsdt_address; + + /* First map just the header to read the length */ + const struct acpi_sdt_header* rsdt_hdr = + (const struct acpi_sdt_header*)acpi_map_phys(rsdt_phys, sizeof(struct acpi_sdt_header)); + if (!rsdt_hdr) { + uart_print("[ACPI] Cannot map RSDT header.\n"); + return -1; + } + uint32_t rsdt_len = rsdt_hdr->length; + acpi_unmap_all(); + + /* Now map the full RSDT */ + const struct acpi_rsdt* rsdt = + (const struct acpi_rsdt*)acpi_map_phys(rsdt_phys, rsdt_len); + if (!rsdt) { + uart_print("[ACPI] Cannot map full RSDT.\n"); + return -1; + } + + if (!acpi_checksum(rsdt, rsdt_len)) { + uart_print("[ACPI] RSDT checksum failed.\n"); + acpi_unmap_all(); + return -1; + } + + /* Search for MADT ("APIC") in RSDT entries */ + uint32_t num_entries = (rsdt_len - sizeof(struct acpi_sdt_header)) / 4; + uintptr_t madt_phys = 0; + + for (uint32_t i = 0; i < num_entries; i++) { + uintptr_t entry_phys = rsdt->entries[i]; + acpi_unmap_all(); + + const struct acpi_sdt_header* hdr = + (const struct acpi_sdt_header*)acpi_map_phys(entry_phys, sizeof(struct acpi_sdt_header)); + if (!hdr) continue; + if (hdr->signature[0] == 'A' && hdr->signature[1] == 'P' && + hdr->signature[2] == 'I' && hdr->signature[3] == 'C') { + madt_phys = entry_phys; + break; + } + + /* Re-map RSDT for next iteration */ + acpi_unmap_all(); + rsdt = (const struct acpi_rsdt*)acpi_map_phys(rsdt_phys, rsdt_len); + if (!rsdt) break; + } + acpi_unmap_all(); + + if (!madt_phys) { + uart_print("[ACPI] MADT not found.\n"); + return -1; + } + + /* Map MADT header to get length, then map full table */ + const struct acpi_sdt_header* madt_hdr = + (const struct acpi_sdt_header*)acpi_map_phys(madt_phys, sizeof(struct acpi_sdt_header)); + if (!madt_hdr) { + uart_print("[ACPI] Cannot map MADT header.\n"); + return -1; + } + uint32_t madt_len = madt_hdr->length; + acpi_unmap_all(); + + const struct acpi_madt* madt = + (const struct acpi_madt*)acpi_map_phys(madt_phys, madt_len); + if (!madt) { + uart_print("[ACPI] Cannot map full MADT.\n"); + return -1; + } + + if (!acpi_checksum(madt, madt_len)) { + uart_print("[ACPI] MADT checksum failed.\n"); + acpi_unmap_all(); + return -1; + } + + if (parse_madt(madt) < 0) { + uart_print("[ACPI] MADT parse failed.\n"); + acpi_unmap_all(); + return -1; + } + acpi_unmap_all(); + + g_acpi_valid = 1; + + /* Print summary */ + uart_print("[ACPI] MADT: "); + itoa(g_acpi_info.num_cpus, tmp, 10); + uart_print(tmp); + uart_print(" CPU(s), LAPIC="); + itoa_hex(g_acpi_info.lapic_address, tmp); + uart_print(tmp); + uart_print(", IOAPIC="); + itoa_hex(g_acpi_info.ioapic_address, tmp); + uart_print(tmp); + uart_print("\n"); + + for (uint8_t i = 0; i < g_acpi_info.num_cpus; i++) { + uart_print("[ACPI] CPU "); + itoa(i, tmp, 10); + uart_print(tmp); + uart_print(": LAPIC ID="); + itoa(g_acpi_info.cpu_lapic_ids[i], tmp, 10); + uart_print(tmp); + uart_print(g_acpi_info.cpu_enabled[i] ? " (enabled)" : " (disabled)"); + uart_print("\n"); + } + + return 0; +} + +const struct acpi_info* acpi_get_info(void) { + if (!g_acpi_valid) return NULL; + return &g_acpi_info; +} diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index ad876f6..956bc48 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -16,6 +16,12 @@ #include "hal/cpu.h" #include "hal/usermode.h" +#if defined(__i386__) +#include "arch/x86/acpi.h" +#include "arch/x86/lapic.h" +#include "arch/x86/ioapic.h" +#endif + #if defined(__i386__) extern void x86_usermode_test_start(void); #endif @@ -80,6 +86,30 @@ int arch_platform_setup(const struct boot_info* bi) { kprintf("[AdrOS] Kernel Initialized (VGA).\n"); syscall_init(); + + /* Parse ACPI tables (MADT) to discover CPU topology and IOAPIC addresses */ + acpi_init(); + + /* Initialize LAPIC + IOAPIC (replaces legacy PIC 8259). + * If APIC is not available, PIC remains active from idt_init(). */ + if (lapic_init()) { + if (ioapic_init()) { + uint32_t bsp_id = lapic_get_id(); + /* Route ISA IRQs through IOAPIC: + * IRQ 0 (PIT/Timer) -> IDT vector 32 + * IRQ 1 (Keyboard) -> IDT vector 33 + * IRQ 14 (ATA primary) -> IDT vector 46 */ + ioapic_route_irq(0, 32, (uint8_t)bsp_id); + ioapic_route_irq(1, 33, (uint8_t)bsp_id); + ioapic_route_irq(14, 46, (uint8_t)bsp_id); + + /* Now that IOAPIC routes are live, disable the legacy PIC. + * This must happen AFTER IOAPIC is configured to avoid + * a window where no interrupt controller handles IRQs. */ + pic_disable(); + } + } + keyboard_init(); return 0; diff --git a/src/arch/x86/idt.c b/src/arch/x86/idt.c index 2ef9b86..c6c3cdf 100644 --- a/src/arch/x86/idt.c +++ b/src/arch/x86/idt.c @@ -1,4 +1,5 @@ #include "arch/x86/idt.h" +#include "arch/x86/lapic.h" #include "io.h" #include "uart_console.h" #include "process.h" @@ -42,6 +43,7 @@ extern void irq8(); extern void irq9(); extern void irq10(); extern void irq11() extern void irq12(); extern void irq13(); extern void irq14(); extern void irq15(); extern void isr128(); +extern void isr255(); void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) { idt[num].base_lo = base & 0xFFFF; @@ -287,6 +289,9 @@ void idt_init(void) { // Syscall gate (int 0x80) must be callable from user mode (DPL=3) idt_set_gate(128, (uint32_t)isr128, 0x08, 0xEE); + // LAPIC spurious interrupt vector (must have an IDT entry or CPU triple-faults) + idt_set_gate(255, (uint32_t)isr255, 0x08, 0x8E); + // Load IDT __asm__ volatile("lidt %0" : : "m"(idtp)); @@ -314,6 +319,26 @@ void print_reg(const char* name, uint32_t val) { // The Main Handler called by assembly void isr_handler(struct registers* regs) { + // LAPIC spurious interrupt (vector 255): do NOT send EOI per Intel spec + if (regs->int_no == 255) { + return; + } + + // Send EOI for IRQs (32-47) BEFORE calling the handler. + // This is critical: the timer handler calls schedule() which may + // context-switch away. If EOI is deferred until after the handler, + // it never executes and the LAPIC blocks all further interrupts. + if (regs->int_no >= 32 && regs->int_no <= 47) { + if (lapic_is_enabled()) { + lapic_eoi(); + } else { + if (regs->int_no >= 40) { + outb(0xA0, 0x20); // Slave EOI + } + outb(0x20, 0x20); // Master EOI + } + } + // Check if we have a custom handler if (interrupt_handlers[regs->int_no] != 0) { isr_handler_t handler = interrupt_handlers[regs->int_no]; @@ -382,14 +407,6 @@ void isr_handler(struct registers* regs) { for(;;) __asm__("hlt"); } } - - // Send EOI (End of Interrupt) to PICs for IRQs (32-47) - if (regs->int_no >= 32 && regs->int_no <= 47) { - if (regs->int_no >= 40) { - outb(0xA0, 0x20); // Slave EOI - } - outb(0x20, 0x20); // Master EOI - } deliver_signals_to_usermode(regs); } diff --git a/src/arch/x86/interrupts.S b/src/arch/x86/interrupts.S index 4f44821..51fc18d 100644 --- a/src/arch/x86/interrupts.S +++ b/src/arch/x86/interrupts.S @@ -134,4 +134,7 @@ IRQ 15, 47 /* Syscall vector (int 0x80 -> 128) */ ISR_NOERRCODE 128 +/* LAPIC spurious interrupt vector (255) */ +ISR_NOERRCODE 255 + .section .note.GNU-stack,"",@progbits diff --git a/src/arch/x86/ioapic.c b/src/arch/x86/ioapic.c new file mode 100644 index 0000000..f162c42 --- /dev/null +++ b/src/arch/x86/ioapic.c @@ -0,0 +1,104 @@ +#include "arch/x86/ioapic.h" +#include "arch/x86/lapic.h" +#include "vmm.h" +#include "uart_console.h" +#include "utils.h" + +#include + +static volatile uint32_t* ioapic_base = 0; +static int ioapic_active = 0; +static uint8_t ioapic_max_irqs = 0; + +static uint32_t ioapic_read(uint32_t reg) { + ioapic_base[IOAPIC_REGSEL / 4] = reg; + return ioapic_base[IOAPIC_REGWIN / 4]; +} + +static void ioapic_write(uint32_t reg, uint32_t val) { + ioapic_base[IOAPIC_REGSEL / 4] = reg; + ioapic_base[IOAPIC_REGWIN / 4] = val; +} + +int ioapic_is_enabled(void) { + return ioapic_active; +} + +int ioapic_init(void) { + if (!lapic_is_enabled()) { + uart_print("[IOAPIC] LAPIC not enabled, skipping IOAPIC.\n"); + return 0; + } + + /* Map IOAPIC MMIO region. + * Default base is 0xFEC00000. In the future, ACPI MADT will provide this. */ + uintptr_t phys_base = IOAPIC_DEFAULT_BASE; + uintptr_t ioapic_va = 0xC0201000U; /* Fixed kernel VA, above _end */ + + vmm_map_page((uint64_t)phys_base, (uint64_t)ioapic_va, + VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE); + ioapic_base = (volatile uint32_t*)ioapic_va; + + /* Read IOAPIC version to get max redirection entries */ + uint32_t ver = ioapic_read(IOAPIC_REG_VER); + ioapic_max_irqs = (uint8_t)(((ver >> 16) & 0xFF) + 1); + if (ioapic_max_irqs > IOAPIC_MAX_IRQS) { + ioapic_max_irqs = IOAPIC_MAX_IRQS; + } + + /* Mask all IRQ lines initially */ + for (uint8_t i = 0; i < ioapic_max_irqs; i++) { + ioapic_mask_irq(i); + } + + ioapic_active = 1; + + uart_print("[IOAPIC] Enabled at phys="); + char tmp[12]; + itoa_hex((uint32_t)phys_base, tmp); + uart_print(tmp); + uart_print(", max IRQs="); + itoa(ioapic_max_irqs, tmp, 10); + uart_print(tmp); + uart_print("\n"); + + return 1; +} + +void ioapic_route_irq(uint8_t irq, uint8_t vector, uint8_t lapic_id) { + if (!ioapic_active) return; + if (irq >= ioapic_max_irqs) return; + + uint32_t reg_lo = IOAPIC_REG_REDTBL + (uint32_t)irq * 2; + uint32_t reg_hi = reg_lo + 1; + + /* High 32 bits: destination LAPIC ID in bits 24-31 */ + ioapic_write(reg_hi, (uint32_t)lapic_id << 24); + + /* Low 32 bits: vector, physical destination, edge-triggered, active-high, unmasked */ + uint32_t lo = (uint32_t)vector; /* vector in bits 0-7 */ + /* bits 8-10: delivery mode = 000 (Fixed) */ + /* bit 11: destination mode = 0 (Physical) */ + /* bit 13: pin polarity = 0 (Active High) */ + /* bit 15: trigger mode = 0 (Edge) */ + /* bit 16: mask = 0 (Unmasked) */ + ioapic_write(reg_lo, lo); +} + +void ioapic_mask_irq(uint8_t irq) { + if (!ioapic_active && ioapic_base == 0) return; + if (irq >= IOAPIC_MAX_IRQS) return; + + uint32_t reg_lo = IOAPIC_REG_REDTBL + (uint32_t)irq * 2; + uint32_t val = ioapic_read(reg_lo); + ioapic_write(reg_lo, val | IOAPIC_RED_MASKED); +} + +void ioapic_unmask_irq(uint8_t irq) { + if (!ioapic_active) return; + if (irq >= ioapic_max_irqs) return; + + uint32_t reg_lo = IOAPIC_REG_REDTBL + (uint32_t)irq * 2; + uint32_t val = ioapic_read(reg_lo); + ioapic_write(reg_lo, val & ~IOAPIC_RED_MASKED); +} diff --git a/src/arch/x86/lapic.c b/src/arch/x86/lapic.c new file mode 100644 index 0000000..e6d867d --- /dev/null +++ b/src/arch/x86/lapic.c @@ -0,0 +1,184 @@ +#include "arch/x86/lapic.h" +#include "hal/cpu_features.h" +#include "vmm.h" +#include "io.h" +#include "uart_console.h" +#include "utils.h" + +#include + +/* IA32_APIC_BASE MSR */ +#define IA32_APIC_BASE_MSR 0x1B +#define IA32_APIC_BASE_ENABLE (1U << 11) +#define IA32_APIC_BASE_ADDR 0xFFFFF000U + +static volatile uint32_t* lapic_base = 0; +static int lapic_active = 0; + +static inline uint64_t rdmsr(uint32_t msr) { + uint32_t lo, hi; + __asm__ volatile("rdmsr" : "=a"(lo), "=d"(hi) : "c"(msr)); + return ((uint64_t)hi << 32) | lo; +} + +static inline void wrmsr(uint32_t msr, uint64_t value) { + uint32_t lo = (uint32_t)(value & 0xFFFFFFFF); + uint32_t hi = (uint32_t)(value >> 32); + __asm__ volatile("wrmsr" : : "c"(msr), "a"(lo), "d"(hi)); +} + +uint32_t lapic_read(uint32_t reg) { + return lapic_base[reg / 4]; +} + +void lapic_write(uint32_t reg, uint32_t val) { + lapic_base[reg / 4] = val; +} + +int lapic_is_enabled(void) { + return lapic_active; +} + +uint32_t lapic_get_id(void) { + if (!lapic_active) return 0; + return (lapic_read(LAPIC_ID) >> 24) & 0xFF; +} + +void lapic_eoi(void) { + if (lapic_active) { + lapic_write(LAPIC_EOI, 0); + } +} + +/* Disable the legacy 8259 PIC by masking all IRQs */ +void pic_disable(void) { + outb(0xA1, 0xFF); /* Mask all slave PIC IRQs */ + outb(0x21, 0xFF); /* Mask all master PIC IRQs */ +} + +int lapic_init(void) { + const struct cpu_features* f = hal_cpu_get_features(); + if (!f->has_apic) { + uart_print("[LAPIC] CPU does not support APIC.\n"); + return 0; + } + if (!f->has_msr) { + uart_print("[LAPIC] CPU does not support MSR.\n"); + return 0; + } + + /* Read APIC base address from MSR */ + uint64_t apic_base_msr = rdmsr(IA32_APIC_BASE_MSR); + uintptr_t phys_base = (uintptr_t)(apic_base_msr & IA32_APIC_BASE_ADDR); + + /* Enable APIC in MSR if not already enabled */ + if (!(apic_base_msr & IA32_APIC_BASE_ENABLE)) { + apic_base_msr |= IA32_APIC_BASE_ENABLE; + wrmsr(IA32_APIC_BASE_MSR, apic_base_msr); + } + + /* Map LAPIC MMIO region into kernel virtual address space. + * Use a fixed kernel VA for the LAPIC page. */ + uintptr_t lapic_va = 0xC0200000U; /* Fixed kernel VA, above _end */ + vmm_map_page((uint64_t)phys_base, (uint64_t)lapic_va, + VMM_FLAG_PRESENT | VMM_FLAG_RW | VMM_FLAG_NOCACHE); + lapic_base = (volatile uint32_t*)lapic_va; + + /* NOTE: Do NOT disable the PIC here. The PIC must remain active + * until the IOAPIC is fully configured with IRQ routes. + * The caller (arch_platform_setup) will disable the PIC after + * IOAPIC setup is complete. */ + + /* Set spurious interrupt vector and enable APIC */ + lapic_write(LAPIC_SVR, LAPIC_SVR_ENABLE | LAPIC_SPURIOUS_VEC); + + /* Clear error status register (write twice per Intel spec) */ + lapic_write(LAPIC_ESR, 0); + lapic_write(LAPIC_ESR, 0); + + /* Set task priority to 0 (accept all interrupts) */ + lapic_write(LAPIC_TPR, 0); + + /* Mask LINT0 and LINT1 */ + lapic_write(LAPIC_LINT0_LVT, LAPIC_LVT_MASKED); + lapic_write(LAPIC_LINT1_LVT, LAPIC_LVT_MASKED); + + /* Mask timer initially */ + lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED); + + /* Send EOI to clear any pending interrupts */ + lapic_eoi(); + + lapic_active = 1; + + uart_print("[LAPIC] Enabled at phys="); + char tmp[12]; + itoa_hex((uint32_t)phys_base, tmp); + uart_print(tmp); + uart_print(", ID="); + itoa(lapic_get_id(), tmp, 10); + uart_print(tmp); + uart_print("\n"); + + return 1; +} + +void lapic_timer_start(uint32_t frequency_hz) { + if (!lapic_active) return; + + /* Use divide-by-16 */ + lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16); + + /* Calibrate: use PIT channel 2 to measure LAPIC timer speed. + * We'll use a simple approach: count how many LAPIC ticks in ~10ms. + * + * PIT channel 2 at 1193180 Hz, count for ~10ms = 11932 ticks. + */ + /* Set PIT channel 2 for one-shot, mode 0 */ + outb(0x61, (inb(0x61) & 0xFD) | 0x01); /* Gate high, speaker off */ + outb(0x43, 0xB0); /* Channel 2, lobyte/hibyte, mode 0 */ + uint16_t pit_count = 11932; /* ~10ms */ + outb(0x42, (uint8_t)(pit_count & 0xFF)); + outb(0x42, (uint8_t)((pit_count >> 8) & 0xFF)); + + /* Reset LAPIC timer to max count */ + lapic_write(LAPIC_TIMER_ICR, 0xFFFFFFFF); + + /* Wait for PIT to count down */ + while (!(inb(0x61) & 0x20)) { + __asm__ volatile("pause"); + } + + /* Stop LAPIC timer */ + lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED); + + /* Read how many ticks elapsed in ~10ms */ + uint32_t elapsed = 0xFFFFFFFF - lapic_read(LAPIC_TIMER_CCR); + + /* Calculate ticks per desired frequency: + * ticks_per_second = elapsed * 100 (since we measured 10ms) + * ticks_per_interrupt = ticks_per_second / frequency_hz */ + uint32_t ticks_per_interrupt = (elapsed * 100) / frequency_hz; + + if (ticks_per_interrupt == 0) ticks_per_interrupt = 1; + + /* Configure periodic timer */ + lapic_write(LAPIC_TIMER_LVT, LAPIC_TIMER_PERIODIC | LAPIC_TIMER_VEC); + lapic_write(LAPIC_TIMER_DCR, LAPIC_TIMER_DIV_16); + lapic_write(LAPIC_TIMER_ICR, ticks_per_interrupt); + + uart_print("[LAPIC] Timer started at "); + char tmp[12]; + itoa((int)frequency_hz, tmp, 10); + uart_print(tmp); + uart_print("Hz (ticks="); + itoa_hex(ticks_per_interrupt, tmp); + uart_print(tmp); + uart_print(")\n"); +} + +void lapic_timer_stop(void) { + if (!lapic_active) return; + lapic_write(LAPIC_TIMER_LVT, LAPIC_LVT_MASKED); + lapic_write(LAPIC_TIMER_ICR, 0); +} diff --git a/src/arch/x86/vmm.c b/src/arch/x86/vmm.c index 9ea6d35..b535672 100644 --- a/src/arch/x86/vmm.c +++ b/src/arch/x86/vmm.c @@ -18,6 +18,8 @@ #define X86_PTE_PRESENT 0x1 #define X86_PTE_RW 0x2 #define X86_PTE_USER 0x4 +#define X86_PTE_PWT 0x8 /* Page Write-Through */ +#define X86_PTE_PCD 0x10 /* Page Cache Disable */ #define X86_PTE_COW 0x200 /* Bit 9: OS-available, marks Copy-on-Write */ /* Defined in boot.S (Physical address loaded in CR3, but accessed via virt alias) */ @@ -36,6 +38,8 @@ static uint32_t vmm_flags_to_x86(uint32_t flags) { if (flags & VMM_FLAG_PRESENT) x86_flags |= X86_PTE_PRESENT; if (flags & VMM_FLAG_RW) x86_flags |= X86_PTE_RW; if (flags & VMM_FLAG_USER) x86_flags |= X86_PTE_USER; + if (flags & VMM_FLAG_PWT) x86_flags |= X86_PTE_PWT; + if (flags & VMM_FLAG_PCD) x86_flags |= X86_PTE_PCD; if (flags & VMM_FLAG_COW) x86_flags |= X86_PTE_COW; return x86_flags; } diff --git a/src/hal/x86/timer.c b/src/hal/x86/timer.c index 4825fb0..99ee816 100644 --- a/src/hal/x86/timer.c +++ b/src/hal/x86/timer.c @@ -2,7 +2,9 @@ #if defined(__i386__) #include "arch/x86/idt.h" +#include "arch/x86/lapic.h" #include "io.h" +#include "uart_console.h" static hal_timer_tick_cb_t g_tick_cb = 0; @@ -16,12 +18,18 @@ void hal_timer_init(uint32_t frequency_hz, hal_timer_tick_cb_t tick_cb) { register_interrupt_handler(32, timer_irq); - uint32_t divisor = 1193180 / frequency_hz; - outb(0x43, 0x36); - uint8_t l = (uint8_t)(divisor & 0xFF); - uint8_t h = (uint8_t)((divisor >> 8) & 0xFF); - outb(0x40, l); - outb(0x40, h); + if (lapic_is_enabled()) { + /* Use LAPIC timer — more precise and per-CPU capable */ + lapic_timer_start(frequency_hz); + } else { + /* Fallback to legacy PIT */ + uint32_t divisor = 1193180 / frequency_hz; + outb(0x43, 0x36); + uint8_t l = (uint8_t)(divisor & 0xFF); + uint8_t h = (uint8_t)((divisor >> 8) & 0xFF); + outb(0x40, l); + outb(0x40, h); + } } #else void hal_timer_init(uint32_t frequency_hz, hal_timer_tick_cb_t tick_cb) { diff --git a/src/kernel/shm.c b/src/kernel/shm.c index eda14e6..6aad260 100644 --- a/src/kernel/shm.c +++ b/src/kernel/shm.c @@ -9,7 +9,9 @@ #include +#ifndef PAGE_SIZE #define PAGE_SIZE 4096U +#endif struct shm_segment { int used;