]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: Fase 7 — ACPI MADT parser + SMP AP bootstrap (INIT-SIPI-SIPI)
authorTulio A M Mendes <[email protected]>
Tue, 10 Feb 2026 09:16:11 +0000 (06:16 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:20:50 +0000 (23:20 -0300)
New files:
- include/arch/x86/acpi.h — ACPI RSDP/RSDT/MADT structures and API
- include/arch/x86/smp.h — SMP per-CPU info and bootstrap API
- src/arch/x86/acpi.c — ACPI table parser: find RSDP in EBDA/BIOS ROM,
  parse RSDT, extract MADT entries (LAPIC, IOAPIC, ISO). Uses temporary
  VMM mappings for tables above the 16MB identity-mapped range.
- src/arch/x86/smp.c — SMP bootstrap: copy 16-bit trampoline to 0x8000,
  patch GDT/CR3/stack/entry, send INIT-SIPI-SIPI per AP, wait for ready.
- src/arch/x86/ap_trampoline.S — 16-bit real-mode AP entry point:
  load GDT, enable protected mode, far-jump to 32-bit, load CR3,
  enable paging, jump to C ap_entry().

Changes:
- include/arch/x86/lapic.h: Add lapic_send_ipi(), rdmsr(), wrmsr() decls
- src/arch/x86/lapic.c: Implement lapic_send_ipi() using ICR_HI/ICR_LO
  with delivery-status polling. Make rdmsr/wrmsr non-static.
- include/arch/x86/gdt.h: Export struct gdt_ptr and gp variable
- src/arch/x86/gdt.c: Make gp non-static for AP trampoline access
- src/arch/x86/linker.ld: Include .ap_trampoline section in .rodata
- src/arch/x86/arch_platform.c: Call acpi_init() then smp_init() after
  LAPIC/IOAPIC setup
- src/arch/x86/idt.c: Move IRQ EOI before handler callback (critical fix:
  schedule() in timer handler context-switches away, blocking LAPIC if
  EOI is deferred). Add IDT gate + ISR stub for spurious vector 255.
- src/arch/x86/interrupts.S: Add ISR_NOERRCODE 255 stub

Key design decisions:
- Trampoline at fixed phys 0x8000, data area at 0x8F00
- APs share BSP's page directory (same CR3)
- APs enable their own LAPIC and halt (idle loop for now)
- ACPI tables mapped via temporary VMM window at 0xC0202000
- Each AP gets a 4KB kernel stack from static array

Tested: -smp 1 (single CPU, all init tests OK)
        -smp 4 (4 CPUs started, all init tests OK)
Passes: make, cppcheck, QEMU smoke test

include/arch/x86/gdt.h
include/arch/x86/lapic.h
include/arch/x86/smp.h [new file with mode: 0644]
src/arch/x86/ap_trampoline.S [new file with mode: 0644]
src/arch/x86/arch_platform.c
src/arch/x86/gdt.c
src/arch/x86/lapic.c
src/arch/x86/linker.ld
src/arch/x86/smp.c [new file with mode: 0644]

index d09969a79573e4b4b4c5328c5abf2b625412017a..4547b64ad7aaf4437bd2d07506b664a0e85e88a1 100644 (file)
@@ -4,6 +4,13 @@
 #include <stdint.h>
 #include <stddef.h>
 
+struct gdt_ptr {
+    uint16_t limit;
+    uint32_t base;
+} __attribute__((packed));
+
+extern struct gdt_ptr gp;
+
 void gdt_init(void);
 void tss_set_kernel_stack(uintptr_t esp0);
 
index f841d1facaf93d32a02801b567e45a92566ec710..9da71ea098779930524b2ffb8bc9ab41a7f24c90 100644 (file)
@@ -65,4 +65,13 @@ int lapic_is_enabled(void);
  * Call AFTER IOAPIC is fully configured with IRQ routes. */
 void pic_disable(void);
 
+/* Send an IPI (Inter-Processor Interrupt) to a specific LAPIC.
+ * dest_id: target LAPIC ID (placed in ICR_HI bits 24-31)
+ * icr_lo:  ICR low word (delivery mode, vector, etc.) */
+void lapic_send_ipi(uint8_t dest_id, uint32_t icr_lo);
+
+/* MSR access helpers (defined in lapic.c) */
+uint64_t rdmsr(uint32_t msr);
+void wrmsr(uint32_t msr, uint64_t val);
+
 #endif
diff --git a/include/arch/x86/smp.h b/include/arch/x86/smp.h
new file mode 100644 (file)
index 0000000..9fb946b
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef ARCH_X86_SMP_H
+#define ARCH_X86_SMP_H
+
+#include <stdint.h>
+
+/* Maximum number of CPUs supported */
+#define SMP_MAX_CPUS 16
+
+/* Per-CPU state */
+struct cpu_info {
+    uint8_t  lapic_id;       /* LAPIC ID */
+    uint8_t  cpu_index;      /* Index in cpu_info array (0 = BSP) */
+    uint8_t  started;        /* 1 if AP has completed init */
+    uint8_t  reserved;
+    uint32_t kernel_stack;   /* Top of this CPU's kernel stack */
+};
+
+/* Initialize SMP: discover APs via ACPI, send INIT-SIPI-SIPI.
+ * Returns the number of CPUs that started (including BSP). */
+int smp_init(void);
+
+/* Get the number of active CPUs. */
+uint32_t smp_get_cpu_count(void);
+
+/* Get cpu_info for a given CPU index. */
+const struct cpu_info* smp_get_cpu(uint32_t index);
+
+/* Get the current CPU's index (based on LAPIC ID). */
+uint32_t smp_current_cpu(void);
+
+#endif
diff --git a/src/arch/x86/ap_trampoline.S b/src/arch/x86/ap_trampoline.S
new file mode 100644 (file)
index 0000000..5fe6a7b
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * AP Trampoline — 16-bit real-mode entry point for Application Processors.
+ *
+ * This code is copied to physical address 0x8000 before sending INIT-SIPI-SIPI.
+ * Each AP starts executing here in 16-bit real mode.
+ *
+ * Data layout at fixed physical addresses (written by BSP before SIPI):
+ *   0x8F00: GDT pointer (6 bytes: limit + phys base)
+ *   0x8F08: CR3 value (page directory physical address, 4 bytes)
+ *   0x8F0C: AP kernel stack top (virtual address, 4 bytes)
+ *   0x8F10: C entry point (virtual address, 4 bytes)
+ */
+
+.set AP_TRAMPOLINE_BASE, 0x8000
+.set AP_DATA_BASE,       0x8F00
+
+.code16
+.section .ap_trampoline, "ax", @progbits
+.global ap_trampoline_start
+.global ap_trampoline_end
+
+ap_trampoline_start:
+    cli
+    cld
+
+    /* DS = 0 so we can use absolute physical addresses */
+    xor %ax, %ax
+    mov %ax, %ds
+
+    /* Load the GDT that the BSP prepared at 0x8F00 */
+    lgdt (AP_DATA_BASE)
+
+    /* Enable protected mode (PE bit in CR0) */
+    mov %cr0, %eax
+    or $1, %eax
+    mov %eax, %cr0
+
+    /* Far jump to 32-bit protected mode code.
+     * 0x08 = kernel code segment selector.
+     * Target address is patched by BSP. */
+    .byte 0x66, 0xEA           /* 32-bit far jump opcode */
+.global ap_pm_target
+ap_pm_target:
+    .long 0                     /* Patched: physical address of ap_pm_entry */
+    .word 0x08                  /* Kernel CS selector */
+
+.code32
+.align 4
+.global ap_pm_entry
+ap_pm_entry:
+    /* Now in 32-bit protected mode, paging OFF, DS base = 0 */
+    mov $0x10, %ax              /* Kernel data segment selector */
+    mov %ax, %ds
+    mov %ax, %es
+    mov %ax, %fs
+    mov %ax, %gs
+    mov %ax, %ss
+
+    /* Load CR3 (page directory) from data area */
+    mov (AP_DATA_BASE + 8), %eax
+    mov %eax, %cr3
+
+    /* Enable paging */
+    mov %cr0, %eax
+    or $0x80000000, %eax
+    mov %eax, %cr0
+
+    /* Now paging is ON — we're still executing from identity-mapped low memory.
+     * Load the virtual stack and jump to the virtual C entry point. */
+    mov (AP_DATA_BASE + 12), %esp
+    mov (AP_DATA_BASE + 16), %eax
+    jmp *%eax
+
+    /* Should never reach here */
+1:  hlt
+    jmp 1b
+
+ap_trampoline_end:
+
+.section .note.GNU-stack,"",@progbits
index 956bc483b88aa819bfa5c9d5ee31d9c566884718..1abfe66b9494a6cca2e61548fe97e2b988dd0bbd 100644 (file)
@@ -20,6 +20,7 @@
 #include "arch/x86/acpi.h"
 #include "arch/x86/lapic.h"
 #include "arch/x86/ioapic.h"
+#include "arch/x86/smp.h"
 #endif
 
 #if defined(__i386__)
@@ -108,6 +109,9 @@ int arch_platform_setup(const struct boot_info* bi) {
              * a window where no interrupt controller handles IRQs. */
             pic_disable();
         }
+
+        /* Bootstrap Application Processors (APs) via INIT-SIPI-SIPI */
+        smp_init();
     }
 
     keyboard_init();
index bde10f041210d92f5cfb87d5b3801f4c53c3af48..b2cf13219ae4f7f4bdabc5dbbfc133c3a5448475 100644 (file)
@@ -12,11 +12,6 @@ struct gdt_entry {
     uint8_t base_high;
 } __attribute__((packed));
 
-struct gdt_ptr {
-    uint16_t limit;
-    uint32_t base;
-} __attribute__((packed));
-
 struct tss_entry {
     uint32_t prev_tss;
     uint32_t esp0;
@@ -51,7 +46,7 @@ extern void gdt_flush(uint32_t gdt_ptr_addr);
 extern void tss_flush(uint16_t tss_selector);
 
 static struct gdt_entry gdt[6];
-static struct gdt_ptr gp;
+struct gdt_ptr gp;
 static struct tss_entry tss;
 
 static void gdt_set_gate(int num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran) {
index e6d867d72c576380ecd1992ace714b7938d1467b..999b0952cae8b4f7494579b6117f318cedcad3a4 100644 (file)
 static volatile uint32_t* lapic_base = 0;
 static int lapic_active = 0;
 
-static inline uint64_t rdmsr(uint32_t msr) {
+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) {
+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));
@@ -50,6 +50,18 @@ void lapic_eoi(void) {
     }
 }
 
+void lapic_send_ipi(uint8_t dest_id, uint32_t icr_lo) {
+    if (!lapic_active) return;
+    /* Write destination LAPIC ID to ICR high (bits 24-31) */
+    lapic_write(LAPIC_ICR_HI, ((uint32_t)dest_id) << 24);
+    /* Write command to ICR low — this triggers the IPI */
+    lapic_write(LAPIC_ICR_LO, icr_lo);
+    /* Wait for delivery (bit 12 = delivery status, 0 = idle) */
+    while (lapic_read(LAPIC_ICR_LO) & (1U << 12)) {
+        __asm__ volatile("pause");
+    }
+}
+
 /* Disable the legacy 8259 PIC by masking all IRQs */
 void pic_disable(void) {
     outb(0xA1, 0xFF);  /* Mask all slave PIC IRQs */
index 82e99da19c57d63cdcd6dcdc1391ea9bd70a51fa..d9211a52153b18b8b0ab8b563725b3c57612d011 100644 (file)
@@ -44,6 +44,7 @@ SECTIONS
     .rodata : AT(ADDR(.rodata) - KERNEL_VIRT_BASE)
     {
         *(.rodata)
+        *(.ap_trampoline)
     } :rodata
 
     . = ALIGN(0x1000);
diff --git a/src/arch/x86/smp.c b/src/arch/x86/smp.c
new file mode 100644 (file)
index 0000000..9ade901
--- /dev/null
@@ -0,0 +1,215 @@
+#include "arch/x86/smp.h"
+#include "arch/x86/acpi.h"
+#include "arch/x86/lapic.h"
+#include "arch/x86/idt.h"
+#include "arch/x86/gdt.h"
+#include "uart_console.h"
+#include "utils.h"
+#include "io.h"
+#include "hal/cpu.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+/* Trampoline symbols from ap_trampoline.S */
+extern uint8_t ap_trampoline_start[];
+extern uint8_t ap_trampoline_end[];
+extern uint8_t ap_pm_entry[];
+extern uint8_t ap_pm_target[];
+
+/* Must match ap_trampoline.S constants */
+#define AP_TRAMPOLINE_PHYS  0x8000U
+#define AP_DATA_PHYS        0x8F00U
+
+/* Per-AP kernel stack size */
+#define AP_STACK_SIZE       4096U
+
+#define KERNEL_VIRT_BASE    0xC0000000U
+
+static struct cpu_info g_cpus[SMP_MAX_CPUS];
+static volatile uint32_t g_cpu_count = 0;
+
+/* AP kernel stacks — statically allocated */
+static uint8_t ap_stacks[SMP_MAX_CPUS][AP_STACK_SIZE] __attribute__((aligned(16)));
+
+/* Rough busy-wait delay */
+static void delay_us(uint32_t us) {
+    volatile uint32_t count = us * 10;
+    while (count--) {
+        __asm__ volatile("pause");
+    }
+}
+
+/* Read the current CR3 value (page directory physical address) */
+static inline uint32_t read_cr3(void) {
+    uint32_t val;
+    __asm__ volatile("mov %%cr3, %0" : "=r"(val));
+    return val;
+}
+
+/* Called by each AP after it enters protected mode + paging.
+ * This runs on the AP's own stack. */
+void ap_entry(void) {
+    /* Enable LAPIC on this AP */
+    uint64_t apic_base_msr = rdmsr(0x1B);
+    if (!(apic_base_msr & (1ULL << 11))) {
+        apic_base_msr |= (1ULL << 11);
+        wrmsr(0x1B, apic_base_msr);
+    }
+
+    /* The LAPIC MMIO is already mapped by the BSP (same page directory).
+     * Set spurious vector and enable. */
+    lapic_write(LAPIC_SVR, LAPIC_SVR_ENABLE | LAPIC_SPURIOUS_VEC);
+    lapic_write(LAPIC_TPR, 0);
+
+    /* Get our LAPIC ID */
+    uint32_t my_id = lapic_get_id();
+
+    /* Find our cpu_info slot and mark started */
+    for (uint32_t i = 0; i < g_cpu_count; i++) {
+        if (g_cpus[i].lapic_id == (uint8_t)my_id) {
+            __atomic_store_n(&g_cpus[i].started, 1, __ATOMIC_SEQ_CST);
+            break;
+        }
+    }
+
+    /* AP is now idle — halt until needed.
+     * In the future, each AP will run its own scheduler. */
+    for (;;) {
+        __asm__ volatile("sti; hlt");
+    }
+}
+
+int smp_init(void) {
+    const struct acpi_info* acpi = acpi_get_info();
+    if (!acpi || acpi->num_cpus <= 1) {
+        g_cpu_count = 1;
+        g_cpus[0].lapic_id = (uint8_t)lapic_get_id();
+        g_cpus[0].cpu_index = 0;
+        g_cpus[0].started = 1;
+        uart_print("[SMP] Single CPU, no APs to start.\n");
+        return 1;
+    }
+
+    /* Populate cpu_info from ACPI */
+    g_cpu_count = acpi->num_cpus;
+    uint8_t bsp_id = (uint8_t)lapic_get_id();
+
+    for (uint8_t i = 0; i < acpi->num_cpus && i < SMP_MAX_CPUS; i++) {
+        g_cpus[i].lapic_id = acpi->cpu_lapic_ids[i];
+        g_cpus[i].cpu_index = i;
+        g_cpus[i].started = (g_cpus[i].lapic_id == bsp_id) ? 1 : 0;
+        g_cpus[i].kernel_stack = (uint32_t)(uintptr_t)&ap_stacks[i][AP_STACK_SIZE];
+    }
+
+    /* Copy trampoline code to 0x8000 (identity-mapped by boot.S) */
+    uint32_t tramp_size = (uint32_t)((uintptr_t)ap_trampoline_end - (uintptr_t)ap_trampoline_start);
+    volatile uint8_t* dest = (volatile uint8_t*)(AP_TRAMPOLINE_PHYS + KERNEL_VIRT_BASE);
+    for (uint32_t i = 0; i < tramp_size; i++) {
+        dest[i] = ap_trampoline_start[i];
+    }
+
+    /* Patch the far-jump target: physical address of ap_pm_entry in the copy */
+    uint32_t pm_entry_offset = (uint32_t)((uintptr_t)ap_pm_entry - (uintptr_t)ap_trampoline_start);
+    uint32_t pm_target_offset = (uint32_t)((uintptr_t)ap_pm_target - (uintptr_t)ap_trampoline_start);
+    volatile uint32_t* jmp_target = (volatile uint32_t*)(
+        AP_TRAMPOLINE_PHYS + KERNEL_VIRT_BASE + pm_target_offset);
+    *jmp_target = AP_TRAMPOLINE_PHYS + pm_entry_offset;
+
+    /* Write data area at 0x8F00 (accessed via identity map + KERNEL_VIRT_BASE) */
+    volatile uint8_t* data = (volatile uint8_t*)(AP_DATA_PHYS + KERNEL_VIRT_BASE);
+
+    /* 0x8F00: GDT pointer (6 bytes) with physical base address */
+    struct gdt_ptr phys_gp;
+    phys_gp.limit = gp.limit;
+    phys_gp.base = gp.base - KERNEL_VIRT_BASE;
+    memcpy((void*)data, &phys_gp, sizeof(phys_gp));
+
+    /* 0x8F08: CR3 (page directory physical address) */
+    uint32_t cr3_val = read_cr3();
+    volatile uint32_t* cr3_ptr   = (volatile uint32_t*)(data + 8);
+    volatile uint32_t* stack_ptr = (volatile uint32_t*)(data + 12);
+    volatile uint32_t* entry_ptr = (volatile uint32_t*)(data + 16);
+
+    *cr3_ptr = cr3_val;
+    *entry_ptr = (uint32_t)(uintptr_t)ap_entry;
+
+    uart_print("[SMP] Starting ");
+    char tmp[12];
+    itoa(g_cpu_count - 1, tmp, 10);
+    uart_print(tmp);
+    uart_print(" AP(s)...\n");
+
+    uint8_t sipi_vector = (uint8_t)(AP_TRAMPOLINE_PHYS >> 12);
+
+    for (uint32_t i = 0; i < g_cpu_count && i < SMP_MAX_CPUS; i++) {
+        if (g_cpus[i].lapic_id == bsp_id) continue;
+        if (!acpi->cpu_enabled[i]) continue;
+
+        /* Set this AP's stack before sending SIPI */
+        *stack_ptr = g_cpus[i].kernel_stack;
+
+        /* INIT IPI */
+        lapic_send_ipi(g_cpus[i].lapic_id, 0x00004500);
+        delay_us(10000);
+
+        /* INIT deassert */
+        lapic_send_ipi(g_cpus[i].lapic_id, 0x00008500);
+        delay_us(200);
+
+        /* First SIPI */
+        lapic_send_ipi(g_cpus[i].lapic_id, 0x00004600 | sipi_vector);
+        delay_us(200);
+
+        /* Second SIPI (per Intel spec) */
+        lapic_send_ipi(g_cpus[i].lapic_id, 0x00004600 | sipi_vector);
+        delay_us(200);
+
+        /* Wait for AP to signal ready (timeout ~1s) */
+        for (uint32_t wait = 0; wait < 100000; wait++) {
+            if (__atomic_load_n(&g_cpus[i].started, __ATOMIC_SEQ_CST)) break;
+            delay_us(10);
+        }
+
+        if (g_cpus[i].started) {
+            uart_print("[SMP] CPU ");
+            itoa(g_cpus[i].lapic_id, tmp, 10);
+            uart_print(tmp);
+            uart_print(" started.\n");
+        } else {
+            uart_print("[SMP] CPU ");
+            itoa(g_cpus[i].lapic_id, tmp, 10);
+            uart_print(tmp);
+            uart_print(" failed to start!\n");
+        }
+    }
+
+    uint32_t started = 0;
+    for (uint32_t i = 0; i < g_cpu_count; i++) {
+        if (g_cpus[i].started) started++;
+    }
+
+    uart_print("[SMP] ");
+    itoa(started, tmp, 10);
+    uart_print(tmp);
+    uart_print(" CPU(s) active.\n");
+
+    return (int)started;
+}
+
+uint32_t smp_get_cpu_count(void) {
+    return g_cpu_count;
+}
+
+const struct cpu_info* smp_get_cpu(uint32_t index) {
+    if (index >= g_cpu_count) return NULL;
+    return &g_cpus[index];
+}
+
+uint32_t smp_current_cpu(void) {
+    uint32_t id = lapic_get_id();
+    for (uint32_t i = 0; i < g_cpu_count; i++) {
+        if (g_cpus[i].lapic_id == (uint8_t)id) return i;
+    }
+    return 0;
+}