From: Tulio A M Mendes Date: Tue, 10 Feb 2026 09:16:11 +0000 (-0300) Subject: feat: Fase 7 — ACPI MADT parser + SMP AP bootstrap (INIT-SIPI-SIPI) X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=8007fe4b0157afdf46a9f2cc9b0693798571ba87;p=AdrOS.git feat: Fase 7 — ACPI MADT parser + SMP AP bootstrap (INIT-SIPI-SIPI) 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 --- diff --git a/include/arch/x86/gdt.h b/include/arch/x86/gdt.h index d09969a..4547b64 100644 --- a/include/arch/x86/gdt.h +++ b/include/arch/x86/gdt.h @@ -4,6 +4,13 @@ #include #include +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); diff --git a/include/arch/x86/lapic.h b/include/arch/x86/lapic.h index f841d1f..9da71ea 100644 --- a/include/arch/x86/lapic.h +++ b/include/arch/x86/lapic.h @@ -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 index 0000000..9fb946b --- /dev/null +++ b/include/arch/x86/smp.h @@ -0,0 +1,31 @@ +#ifndef ARCH_X86_SMP_H +#define ARCH_X86_SMP_H + +#include + +/* 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 index 0000000..5fe6a7b --- /dev/null +++ b/src/arch/x86/ap_trampoline.S @@ -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 diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index 956bc48..1abfe66 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -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(); diff --git a/src/arch/x86/gdt.c b/src/arch/x86/gdt.c index bde10f0..b2cf132 100644 --- a/src/arch/x86/gdt.c +++ b/src/arch/x86/gdt.c @@ -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) { diff --git a/src/arch/x86/lapic.c b/src/arch/x86/lapic.c index e6d867d..999b095 100644 --- a/src/arch/x86/lapic.c +++ b/src/arch/x86/lapic.c @@ -15,13 +15,13 @@ 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 */ diff --git a/src/arch/x86/linker.ld b/src/arch/x86/linker.ld index 82e99da..d9211a5 100644 --- a/src/arch/x86/linker.ld +++ b/src/arch/x86/linker.ld @@ -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 index 0000000..9ade901 --- /dev/null +++ b/src/arch/x86/smp.c @@ -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 +#include + +/* 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; +}