feat: Symmetric Multiprocessing, text editor improvements, merge doom libc, implement math functions

This commit is contained in:
2026-03-23 20:09:11 +01:00
parent a805b06406
commit 63d9270613
46 changed files with 3004 additions and 2404 deletions
+21
View File
@@ -64,6 +64,27 @@ namespace Hal {
<< " max LVT=" << base::dec << (uint64_t)((version >> 16) & 0xFF);
}
void InitializeAP() {
if (!g_apicBase) return;
// Re-assert APIC Global Enable in MSR
uint64_t msrValue = ReadMSR(MSR_APIC_BASE);
if (!(msrValue & (1ULL << 11))) {
msrValue |= (1ULL << 11);
WriteMSR(MSR_APIC_BASE, msrValue);
}
// Enable APIC software enable + set spurious vector
uint32_t svr = ReadRegister(REG_SPURIOUS);
svr |= (1 << 8);
svr = (svr & 0xFFFFFF00) | SPURIOUS_VECTOR;
WriteRegister(REG_SPURIOUS, svr);
// Accept all interrupts
WriteRegister(REG_TPR, 0);
// No log here -- APs boot in parallel and the BSP logs a summary.
}
void Reinitialize() {
if (!g_apicBase) return;
+5
View File
@@ -33,6 +33,11 @@ namespace Hal {
void Initialize(uint64_t apicBasePhys);
// Initialize the Local APIC on an AP.
// Uses the same MMIO base as the BSP (each CPU's APIC is at the same
// physical address but routes to its own hardware).
void InitializeAP();
// Re-enable the Local APIC after S3 resume.
// The MMIO mapping and base address survive (they're in page tables / RAM).
// Only the SVR and TPR hardware registers need reprogramming.
+3 -3
View File
@@ -70,9 +70,9 @@ namespace Hal {
IoApic::RouteIrq(IRQ_KEYBOARD, IRQ_VECTOR_BASE + IRQ_KEYBOARD, bspApicId);
IoApic::RouteIrq(IRQ_MOUSE, IRQ_VECTOR_BASE + IRQ_MOUSE, bspApicId);
// Step 8: Enable interrupts
asm volatile("sti");
// Note: sti is NOT called here. The caller (Main.cpp) enables
// interrupts after setting up per-CPU GS base for SMP/SWAPGS.
KernelLogStream(OK, "APIC") << "APIC subsystem initialized, interrupts enabled";
KernelLogStream(OK, "APIC") << "APIC subsystem initialized";
}
};
+9 -3
View File
@@ -42,10 +42,16 @@ namespace Hal {
// C linkage dispatch function called from assembly stubs
extern "C" void HalIrqDispatch(uint64_t irqNumber) {
// Send EOI BEFORE calling the handler. The handler may context-switch
// (via Tick -> Schedule -> SchedContextSwitch) and never return here.
// If EOI is deferred until after the handler, the LAPIC timer vector
// stays masked on this CPU -- no more timer interrupts, no more
// scheduling, and the system eventually locks up.
// This is safe because the ISR runs with IF=0; the LAPIC won't
// deliver a new interrupt until iretq re-enables them.
Hal::LocalApic::SendEOI();
if (irqNumber < Hal::IRQ_COUNT && Hal::g_irqHandlers[irqNumber] != nullptr) {
Hal::g_irqHandlers[irqNumber]((uint8_t)irqNumber);
}
// Send End of Interrupt to the Local APIC
Hal::LocalApic::SendEOI();
}
+24 -6
View File
@@ -1,7 +1,7 @@
;
; IsrStubs.asm
; Hardware interrupt (IRQ) entry stubs for APIC
; Copyright (c) 2025 Daniel Hammer
; Hardware interrupt (IRQ) entry stubs for APIC (SMP-aware with SWAPGS)
; Copyright (c) 2025-2026 Daniel Hammer
;
[bits 64]
@@ -10,18 +10,27 @@ section .text
; External C++ handler function
extern HalIrqDispatch
; Macro to define an IRQ stub
; Each stub pushes the IRQ number and jumps to the common handler
; ====================================================================
; IRQ stub macro with SWAPGS support
; Each stub checks the saved CS to determine if we came from user mode.
; If so, SWAPGS is executed to switch GS to per-CPU data.
; ====================================================================
%macro IRQ_STUB 1
global IrqStub%1
IrqStub%1:
push rax ; Save rax (we use it for the IRQ number)
mov rax, %1 ; IRQ number
cmp qword [rsp + 8], 0x08 ; check saved CS: kernel?
je %%from_kernel
swapgs ; from user mode: swap to kernel GS
%%from_kernel:
push rax ; save rax (used for IRQ number)
mov rax, %1 ; IRQ number
jmp IrqCommon
%endmacro
; ====================================================================
; Common IRQ handler: saves all general-purpose registers,
; calls the C++ dispatch function, restores registers, and returns.
; ====================================================================
IrqCommon:
; rax is already on the stack and holds the IRQ number
push rcx
@@ -63,10 +72,17 @@ IrqCommon:
pop rcx
pop rax
; SWAPGS back if returning to user mode
cmp qword [rsp + 8], 0x08 ; check saved CS: kernel?
je .from_kernel_exit
swapgs ; returning to user mode: restore user GS
.from_kernel_exit:
iretq
; ====================================================================
; Define stubs for IRQs 0..47 (vectors 32..79)
; 0-23: legacy ISA IRQs via IOAPIC, 24-47: MSI vectors
; ====================================================================
IRQ_STUB 0
IRQ_STUB 1
IRQ_STUB 2
@@ -121,7 +137,9 @@ global IrqStubSpurious
IrqStubSpurious:
iretq
; ====================================================================
; Export the stub table for C++ to reference
; ====================================================================
section .data
global IrqStubTable
IrqStubTable:
+10 -1
View File
@@ -73,10 +73,15 @@ namespace Hal {
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
{
uint64_t cs = GetExceptionCS(i, frame);
bool fromUser = (cs & 3) == 3;
// SWAPGS: if from user mode, GS base is user-defined.
// Swap to kernel per-CPU GS base so scheduler calls work.
if (fromUser) asm volatile("swapgs");
// If the fault originated in user-mode (ring 3), kill the process
// instead of panicking the entire system.
if ((cs & 3) == 3 && Sched::GetCurrentPid() >= 0) {
if (fromUser && Sched::GetCurrentPid() >= 0) {
auto* proc = Sched::GetCurrentProcessPtr();
Kt::KernelLogStream(Kt::ERROR, "Exception")
<< ExceptionStrings[i] << " in process \""
@@ -88,6 +93,10 @@ namespace Hal {
frame->InterruptVector = i;
Panic(ExceptionStrings[i], frame);
}
// Unreachable in practice (user faults exit, kernel faults panic),
// but balance the SWAPGS for correctness.
if (fromUser) asm volatile("swapgs");
}
void LoadIDT(IDTRStruct& idtr) {
+279
View File
@@ -0,0 +1,279 @@
/*
* SmpBoot.cpp
* Symmetric Multiprocessing bootstrap and AP entry
* Copyright (c) 2026 Daniel Hammer
*/
#include "SmpBoot.hpp"
#include <Hal/Apic/Apic.hpp>
#include <Hal/Apic/Interrupts.hpp>
#include <Hal/IDT.hpp>
#include <Hal/MSR.hpp>
#include <Hal/Cpu.hpp>
#include <Memory/Paging.hpp>
#include <Memory/HHDM.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <limine.h>
// Defined in Platform/Limine.hpp (included only by Main.cpp to avoid
// duplicating the LIMINE_BASE_REVISION tag).
extern volatile limine_mp_request mp_request;
#include <Libraries/Memory.hpp>
// Verify assembly offsets match struct layout
static_assert(__builtin_offsetof(Smp::CpuData, selfPtr) == CPUDATA_SELF_PTR, "CpuData offset mismatch: selfPtr");
static_assert(__builtin_offsetof(Smp::CpuData, kernelRsp) == CPUDATA_KERNEL_RSP, "CpuData offset mismatch: kernelRsp");
static_assert(__builtin_offsetof(Smp::CpuData, userRspScratch)== CPUDATA_USER_RSP, "CpuData offset mismatch: userRspScratch");
static_assert(__builtin_offsetof(Smp::CpuData, currentSlot) == CPUDATA_CURRENT_SLOT, "CpuData offset mismatch: currentSlot");
extern "C" void SyscallEntry();
// Assembly helpers (GDT.asm)
extern "C" void LoadGDT(Hal::GDTPointer* ptr);
extern "C" void ReloadSegments();
extern "C" void LoadTR();
using namespace Kt;
namespace Smp {
static CpuData g_cpus[MaxCPUs];
static int g_cpuCount = 0;
CpuData* GetCpuData(int index) {
if (index < 0 || index >= g_cpuCount) return nullptr;
return &g_cpus[index];
}
int GetCpuCount() {
return g_cpuCount;
}
// ====================================================================
// Per-CPU GDT/TSS setup
// ====================================================================
static void SetupPerCpuGdtTss(CpuData& cpu) {
// Zero the TSS
memset(&cpu.cpuTss, 0, sizeof(Hal::TSS64));
cpu.cpuTss.iopbOffset = sizeof(Hal::TSS64);
// Copy the standard GDT layout
cpu.cpuGdt = {
{0xFFFF, 0, 0, 0x00, 0x00, 0}, // 0x00 Null
{0xFFFF, 0, 0, 0x9A, 0xA0, 0}, // 0x08 KernelCode
{0xFFFF, 0, 0, 0x92, 0xA0, 0}, // 0x10 KernelData
{0xFFFF, 0, 0, 0xF2, 0xA0, 0}, // 0x18 UserData
{0xFFFF, 0, 0, 0xFA, 0xA0, 0}, // 0x20 UserCode
{0, 0, 0, 0, 0, 0}, // 0x28 TSS low
{0, 0, 0, 0, 0, 0}, // 0x30 TSS high
};
// Encode the 16-byte TSS descriptor
uint64_t base = (uint64_t)&cpu.cpuTss;
uint32_t limit = sizeof(Hal::TSS64) - 1;
// Low 8 bytes
cpu.cpuGdt.TSS.LimitLow = limit & 0xFFFF;
cpu.cpuGdt.TSS.BaseLow = base & 0xFFFF;
cpu.cpuGdt.TSS.BaseMiddle = (base >> 16) & 0xFF;
cpu.cpuGdt.TSS.AccessByte = 0x89; // Present, 64-bit TSS Available
cpu.cpuGdt.TSS.GranularityByte = (limit >> 16) & 0x0F;
cpu.cpuGdt.TSS.BaseHigh = (base >> 24) & 0xFF;
// High 8 bytes (base[63:32])
uint32_t baseUpper = (uint32_t)(base >> 32);
cpu.cpuGdt.TSSHigh.LimitLow = baseUpper & 0xFFFF;
cpu.cpuGdt.TSSHigh.BaseLow = (baseUpper >> 16) & 0xFFFF;
cpu.cpuGdt.TSSHigh.BaseMiddle = 0;
cpu.cpuGdt.TSSHigh.AccessByte = 0;
cpu.cpuGdt.TSSHigh.GranularityByte = 0;
cpu.cpuGdt.TSSHigh.BaseHigh = 0;
cpu.tss = &cpu.cpuTss;
}
// ====================================================================
// Set GS base MSRs for per-CPU data
// ====================================================================
static constexpr uint32_t IA32_GS_BASE = 0xC0000101;
static constexpr uint32_t IA32_KERNEL_GS_BASE = 0xC0000102;
static void SetGSBase(CpuData* cpu) {
// In kernel mode, GSBASE = per-CPU data pointer.
// KernelGSBASE = 0 (user GS base, swapped in on swapgs).
Hal::WriteMSR(IA32_GS_BASE, (uint64_t)cpu);
Hal::WriteMSR(IA32_KERNEL_GS_BASE, 0);
}
// ====================================================================
// BSP initialization
// ====================================================================
void InitBsp() {
memset(g_cpus, 0, sizeof(g_cpus));
CpuData& bsp = g_cpus[0];
bsp.selfPtr = (uint64_t)&bsp;
bsp.cpuIndex = 0;
bsp.lapicId = Hal::LocalApic::GetId();
bsp.currentSlot = -1;
bsp.started = true;
// BSP uses the global TSS (already set up in PrepareGDT)
bsp.tss = &Hal::g_tss;
// Set GS base for BSP
SetGSBase(&bsp);
g_cpuCount = 1;
KernelLogStream(OK, "SMP") << "BSP initialized (LAPIC ID " << (uint64_t)bsp.lapicId << ")";
}
// ====================================================================
// AP entry point
// Called by Limine when goto_address is written.
// RDI = pointer to limine_mp_info for this CPU.
// Runs on a 64KiB Limine-provided stack.
// ====================================================================
static void ApEntry(limine_mp_info* info) {
// Find our CpuData (stored in extra_argument by BootAPs)
CpuData* cpu = (CpuData*)info->extra_argument;
// --- Load per-CPU GDT ---
Hal::GDTPointer gdtPtr {
.Size = sizeof(Hal::BasicGDT) - 1,
.GDTAddress = (uint64_t)&cpu->cpuGdt,
};
LoadGDT(&gdtPtr);
ReloadSegments();
// --- Load TSS ---
LoadTR();
// --- Load shared IDT ---
Hal::IDTReload();
// --- Switch to kernel page tables ---
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
// --- Enable SSE ---
Hal::EnableSSE();
// --- Set GS base ---
SetGSBase(cpu);
// --- Initialize local APIC ---
Hal::LocalApic::InitializeAP();
// --- Program SYSCALL MSRs ---
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
efer |= 1; // SCE
Hal::WriteMSR(Hal::IA32_EFER, efer);
uint64_t star = (0x0010ULL << 48) | (0x0008ULL << 32);
Hal::WriteMSR(Hal::IA32_STAR, star);
Hal::WriteMSR(Hal::IA32_LSTAR, (uint64_t)SyscallEntry);
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
// --- Program PAT (entry 1 = WC) ---
Hal::InitializePAT();
// --- Calibrate and start APIC timer ---
Timekeeping::ApicTimerInitializeAP();
// --- Signal that we are online ---
cpu->started = true;
// --- Enable interrupts and enter idle loop ---
asm volatile("sti");
for (;;) {
asm volatile("hlt");
}
}
// ====================================================================
// Boot all APs
// ====================================================================
void BootAPs() {
if (mp_request.response == nullptr) {
KernelLogStream(WARNING, "SMP") << "No MP response from bootloader - single CPU mode";
return;
}
auto* resp = mp_request.response;
uint64_t cpuCount = resp->cpu_count;
KernelLogStream(INFO, "SMP") << "Bootloader reports " << cpuCount << " CPU(s), BSP LAPIC ID "
<< (uint64_t)resp->bsp_lapic_id;
if (cpuCount <= 1) {
KernelLogStream(INFO, "SMP") << "Single CPU system - no APs to boot";
return;
}
if (cpuCount > (uint64_t)MaxCPUs) {
KernelLogStream(WARNING, "SMP") << "Clamping CPU count from " << cpuCount << " to " << (uint64_t)MaxCPUs;
cpuCount = MaxCPUs;
}
// Prepare all APs, then wake them all at once. This is safe because:
// - APs use BSP's timer calibration (no PIT contention)
// - APs don't log (no terminal contention)
// - Each AP's init is purely local (GDT, TSS, APIC, MSRs)
int apIndex = 1; // BSP is index 0
for (uint64_t i = 0; i < cpuCount; i++) {
limine_mp_info* info = resp->cpus[i];
if (info->lapic_id == resp->bsp_lapic_id) continue;
if (apIndex >= MaxCPUs) break;
CpuData& ap = g_cpus[apIndex];
ap.selfPtr = (uint64_t)&ap;
ap.cpuIndex = apIndex;
ap.lapicId = info->lapic_id;
ap.currentSlot = -1;
ap.started = false;
SetupPerCpuGdtTss(ap);
info->extra_argument = (uint64_t)&ap;
// Wake this AP (it runs ApEntry in parallel with other APs)
__atomic_store_n(&info->goto_address, (limine_goto_address)ApEntry, __ATOMIC_SEQ_CST);
apIndex++;
}
g_cpuCount = apIndex;
// Wait for all APs to come online
for (int i = 1; i < g_cpuCount; i++) {
volatile bool* flag = &g_cpus[i].started;
uint64_t timeout = 100000000;
while (!*flag && timeout > 0) {
asm volatile("pause");
timeout--;
}
if (!*flag) {
KernelLogStream(ERROR, "SMP") << "AP " << i << " (LAPIC "
<< (uint64_t)g_cpus[i].lapicId << ") failed to start";
}
}
// Count how many actually started
int onlineCount = 1; // BSP
for (int i = 1; i < g_cpuCount; i++) {
if (g_cpus[i].started) onlineCount++;
}
KernelLogStream(OK, "SMP") << onlineCount << " CPU(s) online";
}
}
+64
View File
@@ -0,0 +1,64 @@
/*
* SmpBoot.hpp
* Symmetric Multiprocessing bootstrap and per-CPU data
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Hal/GDT.hpp>
// ====================================================================
// Assembly-visible offsets into CpuData
// These MUST match the struct layout below. Verified by static_assert
// in SmpBoot.cpp.
// ====================================================================
#define CPUDATA_SELF_PTR 0
#define CPUDATA_KERNEL_RSP 8
#define CPUDATA_USER_RSP 16
#define CPUDATA_CURRENT_SLOT 24
namespace Smp {
static constexpr int MaxCPUs = 64;
struct CpuData {
// === Fields accessed by assembly (offsets defined above) ===
uint64_t selfPtr; // offset 0: pointer to self
uint64_t kernelRsp; // offset 8: kernel stack top for SYSCALL
uint64_t userRspScratch; // offset 16: scratch for saving user RSP
int32_t currentSlot; // offset 24: process table slot, -1 = idle
int32_t _pad0; // offset 28: padding
// === C++-only fields ===
int cpuIndex; // 0-based CPU index
uint32_t lapicId; // local APIC ID
uint64_t idleSavedRsp; // RSP saved when switching from idle to process
volatile bool started; // set by AP after init is complete
Hal::TSS64* tss; // pointer to this CPU's TSS
// Per-CPU GDT and TSS (APs use these; BSP uses globals)
Hal::BasicGDT cpuGdt __attribute__((aligned(16)));
Hal::TSS64 cpuTss __attribute__((aligned(16)));
};
// Get the current CPU's data (reads gs:0 which holds the self-pointer)
inline CpuData* GetCurrentCpuData() {
CpuData* ptr;
asm volatile("mov %%gs:0, %0" : "=r"(ptr));
return ptr;
}
// Get CPU data by index
CpuData* GetCpuData(int index);
// Number of online CPUs
int GetCpuCount();
// Initialize BSP per-CPU data (call before interrupts are enabled)
void InitBsp();
// Boot all Application Processors (call after all subsystems ready)
void BootAPs();
}
+20 -19
View File
@@ -1,28 +1,34 @@
;
; SyscallEntry.asm
; SYSCALL/SYSRET entry point and user-mode transition
; Copyright (c) 2025 Daniel Hammer
; SYSCALL/SYSRET entry point and user-mode transition (SMP-aware)
; Copyright (c) 2025-2026 Daniel Hammer
;
[bits 64]
section .text
extern SyscallDispatch
extern g_kernelRsp
; ============================================================
; SyscallEntry — called by the SYSCALL instruction
; ====================================================================
; Per-CPU data offsets (must match CpuData in SmpBoot.hpp)
; ====================================================================
%define CPUDATA_KERNEL_RSP 8
%define CPUDATA_USER_RSP 16
; ====================================================================
; SyscallEntry -- called by the SYSCALL instruction
; RCX = user RIP, R11 = user RFLAGS, RAX = syscall number
; Args: RDI, RSI, RDX, R10, R8, R9
; Interrupts are masked (FMASK clears IF)
; ============================================================
; ====================================================================
global SyscallEntry
SyscallEntry:
mov [rel g_userRsp], rsp ; stash user RSP
mov rsp, [rel g_kernelRsp] ; switch to kernel stack
swapgs ; GS now points to per-CPU CpuData
mov [gs:CPUDATA_USER_RSP], rsp ; save user RSP to per-CPU scratch
mov rsp, [gs:CPUDATA_KERNEL_RSP] ; switch to per-CPU kernel stack
; Build SyscallFrame on kernel stack (push order matches struct)
push qword [rel g_userRsp] ; user_rsp
push qword [gs:CPUDATA_USER_RSP] ; user_rsp
push rcx ; user_rip
push r11 ; user_rflags
push rax ; syscall_nr
@@ -61,13 +67,14 @@ SyscallEntry:
pop rcx ; user RIP
pop rsp ; user RSP
swapgs ; restore user GS
o64 sysret
; ============================================================
; JumpToUserMode initial transition to ring 3 via IRETQ
; ====================================================================
; JumpToUserMode -- initial transition to ring 3 via IRETQ
; RDI = user RIP (entry point)
; RSI = user RSP (top of user stack)
; ============================================================
; ====================================================================
global JumpToUserMode
JumpToUserMode:
mov ax, 0x1B ; UserData | RPL3
@@ -79,11 +86,5 @@ JumpToUserMode:
push 0x202 ; RFLAGS (IF=1)
push 0x23 ; CS = UserCode | RPL3
push rdi ; RIP = entry point
swapgs ; switch from kernel GS to user GS
iretq
; ============================================================
; BSS: scratch space for user RSP save
; ============================================================
section .bss
global g_userRsp
g_userRsp: resq 1