]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: Fase 6 — LAPIC + IOAPIC drivers, replace legacy PIC 8259
authorTulio A M Mendes <[email protected]>
Tue, 10 Feb 2026 08:40:28 +0000 (05:40 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:20:50 +0000 (23:20 -0300)
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).

13 files changed:
include/arch/x86/acpi.h [new file with mode: 0644]
include/arch/x86/ioapic.h [new file with mode: 0644]
include/arch/x86/lapic.h [new file with mode: 0644]
include/vmm.h
src/arch/x86/acpi.c [new file with mode: 0644]
src/arch/x86/arch_platform.c
src/arch/x86/idt.c
src/arch/x86/interrupts.S
src/arch/x86/ioapic.c [new file with mode: 0644]
src/arch/x86/lapic.c [new file with mode: 0644]
src/arch/x86/vmm.c
src/hal/x86/timer.c
src/kernel/shm.c

diff --git a/include/arch/x86/acpi.h b/include/arch/x86/acpi.h
new file mode 100644 (file)
index 0000000..d3b6288
--- /dev/null
@@ -0,0 +1,105 @@
+#ifndef ARCH_X86_ACPI_H
+#define ARCH_X86_ACPI_H
+
+#include <stdint.h>
+
+/* 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 (file)
index 0000000..e741d47
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef ARCH_X86_IOAPIC_H
+#define ARCH_X86_IOAPIC_H
+
+#include <stdint.h>
+
+/* 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 (file)
index 0000000..f841d1f
--- /dev/null
@@ -0,0 +1,68 @@
+#ifndef ARCH_X86_LAPIC_H
+#define ARCH_X86_LAPIC_H
+
+#include <stdint.h>
+
+/* 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
index 3ef656015e0c68db2db2b5137e7166795e873e4f..a7efbbb70ac7b75334f1bf66230579c41f15f716 100644 (file)
@@ -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 (file)
index 0000000..f6704b1
--- /dev/null
@@ -0,0 +1,279 @@
+#include "arch/x86/acpi.h"
+#include "uart_console.h"
+#include "utils.h"
+#include "vmm.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+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;
+}
index ad876f6561cdba5e1279445901da041a5e10e032..956bc483b88aa819bfa5c9d5ee31d9c566884718 100644 (file)
 #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;
index 2ef9b86e4db0fd0f863bad40c53ee2d8d8c7433b..c6c3cdfb8367fe68dfbed98d865376db9788a1d2 100644 (file)
@@ -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);
 }
index 4f448211ccfc3065d0ab3636d680904d4e7f9977..51fc18df97d7ed869de0d13ad3c813772e37abc4 100644 (file)
@@ -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 (file)
index 0000000..f162c42
--- /dev/null
@@ -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 <stdint.h>
+
+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 (file)
index 0000000..e6d867d
--- /dev/null
@@ -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 <stdint.h>
+
+/* 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);
+}
index 9ea6d35060855e8e5f3a46ccafb26c23a6e38600..b5356725122ff613f2d8e69ba362c74b88dc9f9c 100644 (file)
@@ -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;
 }
index 4825fb072617785ed49d533bb5a3ed8badc7dc5f..99ee816b4dc36d6c7f481675a5e3abc9de3433d2 100644 (file)
@@ -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) {
index eda14e613e9d5cfb30b3aace659e4a2ff67fbd9c..6aad260cd1794ab788cab79e511feb8211ca36bf 100644 (file)
@@ -9,7 +9,9 @@
 
 #include <stddef.h>
 
+#ifndef PAGE_SIZE
 #define PAGE_SIZE 4096U
+#endif
 
 struct shm_segment {
     int        used;