--- /dev/null
+#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
--- /dev/null
+#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
--- /dev/null
+#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
#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 */
/*
--- /dev/null
+#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;
+}
#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
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;
#include "arch/x86/idt.h"
+#include "arch/x86/lapic.h"
#include "io.h"
#include "uart_console.h"
#include "process.h"
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;
// 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));
// 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];
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);
}
/* Syscall vector (int 0x80 -> 128) */
ISR_NOERRCODE 128
+/* LAPIC spurious interrupt vector (255) */
+ISR_NOERRCODE 255
+
.section .note.GNU-stack,"",@progbits
--- /dev/null
+#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);
+}
--- /dev/null
+#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);
+}
#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) */
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;
}
#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;
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) {
#include <stddef.h>
+#ifndef PAGE_SIZE
#define PAGE_SIZE 4096U
+#endif
struct shm_segment {
int used;