feat: make kernel architecture-agnostic; primitive work on arm port
This commit is contained in:
1 parent
dab7d5427b
commit
1bae77ec84
164 files changed
+3664
-1641
No files matched your search
+48
-1
@@ -27,6 +27,20 @@ ifeq ($(ARCH),x86_64)
|
||||
endif
|
||||
endif
|
||||
|
||||
# The other architectures have no in-tree cross GCC: default to LLVM, which
|
||||
# cross-compiles to every target (linking needs ld.lld; see docs/porting.md).
|
||||
ifneq ($(ARCH),x86_64)
|
||||
ifeq ($(filter command line environment,$(origin CC)),)
|
||||
CC := clang
|
||||
endif
|
||||
ifeq ($(filter command line environment,$(origin CXX)),)
|
||||
CXX := clang++
|
||||
endif
|
||||
ifeq ($(filter command line environment,$(origin AR)),)
|
||||
AR := ar
|
||||
endif
|
||||
endif
|
||||
|
||||
# User controllable C compiler command.
|
||||
CC ?= cc
|
||||
|
||||
@@ -197,8 +211,10 @@ override CXXFLAGS += \
|
||||
-std=gnu++20 \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-fmodules-ts \
|
||||
$(SHARED_FLAGS)
|
||||
ifneq ($(CXX_IS_CLANG),1)
|
||||
override CXXFLAGS += -fmodules-ts
|
||||
endif
|
||||
|
||||
# Internal linker flags that should not be changed by the user.
|
||||
override LDFLAGS += \
|
||||
@@ -212,6 +228,9 @@ override LDFLAGS += \
|
||||
# Use "find" to glob all *.c, *.cpp, *.S, and *.asm files in the tree and obtain the
|
||||
# object and header dependency file names.
|
||||
override SRCFILES := $(shell cd src && find -L * -type f | LC_ALL=C sort)
|
||||
# Architecture-specific code lives in src/Arch/<arch>/; only the target's
|
||||
# directory is built. Everything else in src/ is architecture-neutral.
|
||||
override SRCFILES := $(filter-out Arch/%,$(SRCFILES)) $(filter Arch/$(ARCH)/%,$(SRCFILES))
|
||||
override CFILES := $(filter %.c,$(SRCFILES))
|
||||
override CXXFILES := $(filter %.cpp,$(SRCFILES))
|
||||
override OBJCFILES := $(filter %.mm,$(SRCFILES))
|
||||
@@ -253,6 +272,11 @@ $(BUILDNO_HDR): $(filter-out $(BUILDNO_OBJ),$(OBJ)) $(PROG_SRC)
|
||||
# on a clean build (where no .d dependency exists yet).
|
||||
$(BUILDNO_OBJ): $(BUILDNO_HDR)
|
||||
|
||||
# Compile everything without linking (used by scripts/check-arch-neutral.sh
|
||||
# to verify a port's objects before a cross linker is available).
|
||||
.PHONY: objects
|
||||
objects: $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a
|
||||
|
||||
# Link rules for building the C compiler runtime.
|
||||
cc-runtime-$(ARCH)/cc-runtime.a: GNUmakefile cc-runtime/*
|
||||
rm -rf cc-runtime-$(ARCH)
|
||||
@@ -263,10 +287,33 @@ cc-runtime-$(ARCH)/cc-runtime.a: GNUmakefile cc-runtime/*
|
||||
CFLAGS="$(CFLAGS)" \
|
||||
CPPFLAGS='-isystem ../freestnd-c-hdrs/$(ARCH)/include -DCC_RUNTIME_NO_FLOAT'
|
||||
|
||||
# Clang's generic-ELF driver links cross targets through the host's GCC,
|
||||
# so a clang-built non-x86 kernel is linked by ld.lld directly.
|
||||
ifneq ($(ARCH),x86_64)
|
||||
ifeq ($(CXX_IS_CLANG),1)
|
||||
KERNEL_LD ?= ld.lld
|
||||
override LD_EMULATION_aarch64 := aarch64elf
|
||||
override LD_EMULATION_riscv64 := elf64lriscv
|
||||
override LD_EMULATION_loongarch64 := elf64loongarch
|
||||
override KERNEL_LDFLAGS := \
|
||||
-m $(LD_EMULATION_$(ARCH)) \
|
||||
--build-id=none \
|
||||
--no-relax \
|
||||
-static \
|
||||
-z max-page-size=0x1000 \
|
||||
--gc-sections \
|
||||
-T linker-$(ARCH).ld
|
||||
endif
|
||||
endif
|
||||
|
||||
# Link rules for the final executable.
|
||||
bin-$(ARCH)/$(OUTPUT): GNUmakefile linker-$(ARCH).ld $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a
|
||||
mkdir -p "$$(dirname $@)"
|
||||
ifeq ($(KERNEL_LD),)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a -o $@
|
||||
else
|
||||
$(KERNEL_LD) $(KERNEL_LDFLAGS) $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a -o $@
|
||||
endif
|
||||
|
||||
# Compilation rules for *.c files.
|
||||
obj-$(ARCH)/%.c.o: src/%.c GNUmakefile
|
||||
|
||||
@@ -22,6 +22,7 @@ SECTIONS
|
||||
/* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
|
||||
/* that is the beginning of the region. */
|
||||
. = 0xffffffff80000000;
|
||||
KernelStartSymbol = .;
|
||||
|
||||
/* Define a section to contain the Limine requests and assign it to its own PHDR */
|
||||
.limine_requests : {
|
||||
@@ -74,4 +75,6 @@ SECTIONS
|
||||
*(.eh_frame*)
|
||||
*(.note .note.*)
|
||||
}
|
||||
|
||||
KernelEndSymbol = .;
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@
|
||||
#include "AcpiEvents.hpp"
|
||||
#include <ACPI/AcpiSleep.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/IoApic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Arch/Interrupts.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
@@ -191,24 +189,21 @@ namespace Hal {
|
||||
// routed. Clear stale status only after the enables are down.
|
||||
DisableAndClearGpes();
|
||||
|
||||
// Route SCI to an IRQ vector. The SCI is level-triggered,
|
||||
// Route the SCI to its IRQ slot. The SCI is level-triggered,
|
||||
// active-low (ACPI spec requirement). The MADT may have an
|
||||
// Interrupt Source Override for this IRQ that already sets
|
||||
// these flags; RouteIrq applies overrides automatically.
|
||||
uint8_t sciVector = IRQ_VECTOR_BASE + (uint8_t)g_sciIrq;
|
||||
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
|
||||
// these flags; RouteLegacyIrq applies overrides automatically.
|
||||
|
||||
// Register the handler before routing to avoid missing events
|
||||
RegisterIrqHandler((uint8_t)g_sciIrq, SciHandler);
|
||||
Arch::RegisterIrqHandler((uint8_t)g_sciIrq, SciHandler);
|
||||
|
||||
// Route via IoApic (applies MADT overrides for polarity/trigger)
|
||||
IoApic::RouteIrq((uint8_t)g_sciIrq, sciVector, bspApicId);
|
||||
// Route to the boot CPU (applies firmware polarity/trigger overrides)
|
||||
Arch::RouteLegacyIrq((uint8_t)g_sciIrq);
|
||||
|
||||
g_initialized = true;
|
||||
|
||||
KernelLogStream(OK, "ACPI") << "ACPI events initialized (SCI IRQ "
|
||||
<< base::dec << (uint64_t)g_sciIrq << ", vector "
|
||||
<< (uint64_t)sciVector << ")";
|
||||
<< base::dec << (uint64_t)g_sciIrq << ")";
|
||||
}
|
||||
|
||||
void Reinitialize() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "AcpiShutdown.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include "AcpiSleep.hpp"
|
||||
#include "AcpiEvents.hpp"
|
||||
#include <ACPI/FADT.hpp>
|
||||
@@ -95,7 +96,7 @@ namespace Hal {
|
||||
}
|
||||
}
|
||||
|
||||
asm volatile("cli");
|
||||
Arch::DisableInterrupts();
|
||||
|
||||
// Phase 1: Write SLP_TYP to PM1x_CNT without SLP_EN,
|
||||
// preserving existing register bits
|
||||
@@ -110,7 +111,7 @@ namespace Hal {
|
||||
}
|
||||
|
||||
// Flush all caches before entering sleep state
|
||||
asm volatile("wbinvd");
|
||||
Arch::WritebackInvalidateCaches();
|
||||
|
||||
// Phase 2: Assert SLP_EN to trigger the transition
|
||||
Io::Out16(pm1a | SLP_EN, (uint16_t)g_pm1aControlBlock);
|
||||
@@ -122,7 +123,7 @@ namespace Hal {
|
||||
|
||||
// Halt and wait for the hardware to power off
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
Arch::Halt();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,21 +75,6 @@ namespace Hal {
|
||||
|
||||
// CPU State (saved across S3)
|
||||
|
||||
// ============================================================================
|
||||
// Layout must match S3Wake.asm offsets exactly.
|
||||
// No FPU/SSE state — the kernel is compiled with -mno-sse.
|
||||
struct CpuState {
|
||||
uint64_t Rax, Rbx, Rcx, Rdx; // 0x00 - 0x18
|
||||
uint64_t Rsi, Rdi, Rbp, Rsp; // 0x20 - 0x38
|
||||
uint64_t R8, R9, R10, R11; // 0x40 - 0x58
|
||||
uint64_t R12, R13, R14, R15; // 0x60 - 0x78
|
||||
uint64_t Rflags; // 0x80
|
||||
uint64_t Rip; // 0x88
|
||||
uint64_t Cr3; // 0x90
|
||||
uint64_t Cr0, Cr4; // 0x98, 0xA0
|
||||
uint8_t GdtPtr[10]; // 0xA8
|
||||
uint8_t IdtPtr[10]; // 0xB2
|
||||
} __attribute__((aligned(16)));
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -109,5 +94,9 @@ namespace Hal {
|
||||
// Returns 0 on successful resume, or -1 if S3 is unavailable.
|
||||
int Suspend();
|
||||
|
||||
// Initialize/IsS3Available/Suspend are implemented by the
|
||||
// architecture (the FACS waking-vector path is an x86 platform
|
||||
// mechanism); other targets report S3 as unavailable.
|
||||
|
||||
};
|
||||
};
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "AcpiDevices.hpp"
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
|
||||
+10
-16
@@ -6,8 +6,6 @@
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Hal/Apic/ApicInit.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Drivers/USB/Xhci.hpp>
|
||||
#include <Drivers/USB/UsbDevice.hpp>
|
||||
#include <Drivers/USB/Bluetooth/Bluetooth.hpp>
|
||||
@@ -18,6 +16,9 @@
|
||||
#include <Drivers/Storage/Ahci.hpp>
|
||||
#include <Drivers/Audio/IntelHda.hpp>
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Arch/Arch.hpp>
|
||||
#include <Arch/Platform.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
#include <Drivers/Storage/BlockDevice.hpp>
|
||||
@@ -69,28 +70,21 @@ namespace montauk::abi {
|
||||
};
|
||||
|
||||
// CPU cores (category 0)
|
||||
int cpuCount = Hal::GetDetectedCpuCount();
|
||||
int cpuCount = Smp::GetDetectedCpuCount();
|
||||
if (cpuCount > 0) {
|
||||
char detail[48];
|
||||
int p = 0;
|
||||
p = dl_append(detail, p, "x86_64, ", 48);
|
||||
p = dl_append(detail, p, MONTAUK_ARCH_NAME ", ", 48);
|
||||
p = dl_append_dec(detail, p, cpuCount, 48);
|
||||
p = dl_append(detail, p, " core(s)", 48);
|
||||
add(0, "Processor", detail);
|
||||
}
|
||||
|
||||
// Interrupt controllers (category 1)
|
||||
add(1, "Local APIC", "");
|
||||
add(1, "I/O APIC", "");
|
||||
|
||||
// Timer (category 2)
|
||||
add(2, "LAPIC Timer", "Local APIC periodic timer");
|
||||
|
||||
// PS/2 Input (category 3)
|
||||
add(3, "PS/2 Keyboard", "IRQ 1");
|
||||
if (Drivers::PS2::IsDualChannel()) {
|
||||
add(3, "PS/2 Mouse", "IRQ 12");
|
||||
}
|
||||
// Interrupt controllers (category 1), timer (2), platform input (3)
|
||||
Arch::ListPlatformDevices([](void* ctx, uint8_t category,
|
||||
const char* name, const char* detail) {
|
||||
(*(decltype(add)*)ctx)(category, name, detail);
|
||||
}, &add);
|
||||
|
||||
// USB devices (category 4)
|
||||
if (Drivers::USB::Xhci::IsInitialized()) {
|
||||
|
||||
@@ -94,14 +94,14 @@ namespace montauk::abi {
|
||||
proc->readdirCursor = (slot + 1) % Sched::UserReadDirSlots;
|
||||
|
||||
uint64_t userVa = Sched::UserReadDirBase + (uint64_t)slot * 0x1000ULL;
|
||||
uint64_t physAddr = Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, userVa);
|
||||
uint64_t physAddr = Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot, userVa);
|
||||
uint8_t* pageBuf = nullptr;
|
||||
|
||||
if (physAddr == 0) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) return OutOfMemory;
|
||||
physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
if (!Memory::VMM::Paging::MapUserIn(proc->pml4Phys, physAddr, userVa)) {
|
||||
if (!Memory::VMM::Paging::MapUserIn(proc->pageTableRoot, physAddr, userVa)) {
|
||||
Memory::g_pfa->Free(page);
|
||||
return OutOfMemory;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace montauk::abi {
|
||||
|
||||
for (uint64_t i = 0; i < numPages; i++) {
|
||||
if (!Memory::VMM::Paging::MapUserInWC(
|
||||
proc->pml4Phys,
|
||||
proc->pageTableRoot,
|
||||
fbPhys + i * 0x1000,
|
||||
userVa + i * 0x1000
|
||||
)) {
|
||||
@@ -98,7 +98,7 @@ namespace montauk::abi {
|
||||
uint64_t buf1Phys = Drivers::Graphics::IntelGPU::GetBufferPhys(1);
|
||||
for (uint64_t i = 0; i < numPages; i++) {
|
||||
if (!Memory::VMM::Paging::MapUserInWC(
|
||||
proc->pml4Phys,
|
||||
proc->pageTableRoot,
|
||||
buf1Phys + i * 0x1000,
|
||||
userVa + (numPages + i) * 0x1000
|
||||
)) {
|
||||
|
||||
+15
-15
@@ -107,7 +107,7 @@ namespace montauk::abi {
|
||||
if (page == nullptr) break;
|
||||
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||
if (!Memory::VMM::Paging::MapUserInPermissions(
|
||||
proc->pml4Phys, phys, pageVa, writable, executable)) {
|
||||
proc->pageTableRoot, phys, pageVa, writable, executable)) {
|
||||
Memory::g_pfa->Free(page);
|
||||
break;
|
||||
}
|
||||
@@ -131,7 +131,7 @@ namespace montauk::abi {
|
||||
|
||||
// Reset heap allocation tracking for a process slot.
|
||||
// The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup.
|
||||
inline void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) {
|
||||
inline void CleanupHeapForSlot(int slot, uint64_t /*pageTableRoot*/) {
|
||||
if (slot < 0 || slot >= Sched::MaxProcesses) return;
|
||||
g_heapLocks[slot].Acquire();
|
||||
HeapAlloc* area = g_heapAllocs[slot];
|
||||
@@ -182,7 +182,7 @@ namespace montauk::abi {
|
||||
}
|
||||
for (uint64_t i = 0; i < area->numPages; i++)
|
||||
if (Memory::VMM::Paging::GetPhysAddr(
|
||||
proc->pml4Phys, area->va + i * 0x1000ULL) != 0)
|
||||
proc->pageTableRoot, area->va + i * 0x1000ULL) != 0)
|
||||
resident++;
|
||||
*link = area->next;
|
||||
area->next = released;
|
||||
@@ -194,7 +194,7 @@ namespace montauk::abi {
|
||||
// user TLB entry can otherwise corrupt the frame's next owner.
|
||||
while (released != nullptr) {
|
||||
HeapAlloc* next = released->next;
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, released->va,
|
||||
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, released->va,
|
||||
released->numPages);
|
||||
Sched::ReleaseUserHeapRange(slot, released->va,
|
||||
released->numPages * 0x1000ULL);
|
||||
@@ -253,11 +253,11 @@ namespace montauk::abi {
|
||||
|
||||
uint64_t resident = 0;
|
||||
for (uint64_t i = 0; i < pages; i++)
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, addr + i * 0x1000ULL) != 0)
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot, addr + i * 0x1000ULL) != 0)
|
||||
resident++;
|
||||
g_heapLocks[slot].Release();
|
||||
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, addr, pages);
|
||||
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, addr, pages);
|
||||
Sched::ReleaseUserHeapRange(slot, addr, size);
|
||||
g_heapLocks[slot].Acquire();
|
||||
Sched::g_allocatedPages[slot] -= resident;
|
||||
@@ -336,10 +336,10 @@ namespace montauk::abi {
|
||||
bool writable = (prot & VmProtWrite) != 0;
|
||||
bool executable = (prot & VmProtExec) != 0;
|
||||
for (uint64_t i = 0; i < pages; i++) {
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys,
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot,
|
||||
addr + i * 0x1000ULL) == 0)
|
||||
continue;
|
||||
if (!Memory::VMM::Paging::ProtectUserIn(proc->pml4Phys,
|
||||
if (!Memory::VMM::Paging::ProtectUserIn(proc->pageTableRoot,
|
||||
addr + i * 0x1000ULL,
|
||||
writable, executable)) {
|
||||
g_heapLocks[slot].Release();
|
||||
@@ -347,17 +347,17 @@ namespace montauk::abi {
|
||||
}
|
||||
}
|
||||
g_heapLocks[slot].Release();
|
||||
Memory::ShootdownUserRange(proc->pml4Phys, addr, (uint32_t)pages);
|
||||
Memory::ShootdownUserRange(proc->pageTableRoot, addr, (uint32_t)pages);
|
||||
return Success;
|
||||
}
|
||||
|
||||
// Resolve a non-present access inside an anonymous VMA. This is used for
|
||||
// both ring-3 faults and kernel copies to untouched userspace buffers.
|
||||
inline bool TryHandleAnonymousPageFault(uint64_t faultAddr, uint64_t errorCode) {
|
||||
inline bool TryHandleAnonymousPageFault(uint64_t faultAddr, const Memory::VMM::PageFault& fault) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
int slot = GetCurrentSlot();
|
||||
if (proc == nullptr || slot < 0 || faultAddr < Sched::UserHeapBase ||
|
||||
faultAddr >= Sched::UserHeapLimit || (errorCode & 1) != 0) return false;
|
||||
faultAddr >= Sched::UserHeapLimit || fault.present) return false;
|
||||
|
||||
uint64_t pageVa = faultAddr & ~0xFFFULL;
|
||||
g_heapLocks[slot].Acquire();
|
||||
@@ -370,14 +370,14 @@ namespace montauk::abi {
|
||||
}
|
||||
}
|
||||
if (found == nullptr ||
|
||||
((errorCode & (1ULL << 1)) != 0 && (found->prot & VmProtWrite) == 0) ||
|
||||
((errorCode & (1ULL << 4)) != 0 && (found->prot & VmProtExec) == 0)) {
|
||||
(fault.write && (found->prot & VmProtWrite) == 0) ||
|
||||
(fault.execute && (found->prot & VmProtExec) == 0)) {
|
||||
g_heapLocks[slot].Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
// A sibling may have resolved the same fault while this CPU waited.
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pml4Phys, pageVa) != 0) {
|
||||
if (Memory::VMM::Paging::GetPhysAddr(proc->pageTableRoot, pageVa) != 0) {
|
||||
g_heapLocks[slot].Release();
|
||||
return true;
|
||||
}
|
||||
@@ -389,7 +389,7 @@ namespace montauk::abi {
|
||||
}
|
||||
uint64_t phys = Memory::SubHHDM((uint64_t)page);
|
||||
bool mapped = Memory::VMM::Paging::MapUserInPermissions(
|
||||
proc->pml4Phys, phys, pageVa,
|
||||
proc->pageTableRoot, phys, pageVa,
|
||||
(found->prot & VmProtWrite) != 0, (found->prot & VmProtExec) != 0);
|
||||
if (!mapped) Memory::g_pfa->Free(page);
|
||||
else Sched::g_allocatedPages[slot]++;
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace montauk::abi {
|
||||
}
|
||||
|
||||
// Load the library into the process's address space
|
||||
uint64_t loadBias = Sched::ElfLoadLib(path, proc->pml4Phys, libSlot);
|
||||
uint64_t loadBias = Sched::ElfLoadLib(path, proc->pageTableRoot, libSlot);
|
||||
if (loadBias == 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Lib") << "Failed to load library: " << path;
|
||||
g_libLocks[slot].Release();
|
||||
@@ -134,7 +134,7 @@ namespace montauk::abi {
|
||||
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc != nullptr) {
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
||||
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, libBase,
|
||||
(libEnd - libBase) / 0x1000ULL);
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ namespace montauk::abi {
|
||||
uint64_t libBase = GetLibSlotBase(i);
|
||||
uint64_t libEnd = libBase + Sched::LIB_MAX_SIZE;
|
||||
|
||||
Memory::UnmapAndFreeUserRange(proc->pml4Phys, libBase,
|
||||
Memory::UnmapAndFreeUserRange(proc->pageTableRoot, libBase,
|
||||
(libEnd - libBase) / 0x1000ULL);
|
||||
|
||||
g_libTable[slot][i].inUse = false;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Net/Icmp.hpp>
|
||||
#include <Net/Dns.hpp>
|
||||
#include <Net/Ipv4.hpp>
|
||||
|
||||
+12
-15
@@ -8,13 +8,13 @@
|
||||
#include <cstdint>
|
||||
#include <Efi/UEFI.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Graphics/Framebuffer.hpp>
|
||||
#include <ACPI/AcpiShutdown.hpp>
|
||||
#include <ACPI/AcpiSleep.hpp>
|
||||
#include <ACPI/CpuIdle.hpp>
|
||||
#include <ACPI/Battery.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/CpuPower.hpp>
|
||||
#include <Arch/Platform.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -56,15 +56,13 @@ namespace montauk::abi {
|
||||
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
|
||||
<< (uint64_t)Sched::GetCurrentPid() << " entered final reboot stage";
|
||||
if (Efi::g_ResetSystem) {
|
||||
/* Switch to kernel PML4 which has identity-mapped UEFI runtime regions */
|
||||
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
|
||||
/* Switch to the kernel address space, which has identity-mapped UEFI runtime regions */
|
||||
Memory::VMM::ActivateKernelAddressSpace();
|
||||
Efi::g_ResetSystem(Efi::EfiResetCold, 0, 0, nullptr);
|
||||
}
|
||||
|
||||
/* Fallback: triple fault via null IDT */
|
||||
struct [[gnu::packed]] { uint16_t limit; uint64_t base; } nullIdt = {0, 0};
|
||||
asm volatile("lidt %0; int $0x03" :: "m"(nullIdt));
|
||||
__builtin_unreachable();
|
||||
/* Fallback: architecture-specific reset */
|
||||
Arch::Reboot();
|
||||
}
|
||||
|
||||
static void Sys_Shutdown() {
|
||||
@@ -77,13 +75,12 @@ namespace montauk::abi {
|
||||
|
||||
/* Fallback: EFI ResetSystem */
|
||||
if (Efi::g_ResetSystem) {
|
||||
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
|
||||
Memory::VMM::ActivateKernelAddressSpace();
|
||||
Efi::g_ResetSystem(Efi::EfiResetShutdown, 0, 0, nullptr);
|
||||
}
|
||||
|
||||
/* Last resort: halt the CPU */
|
||||
asm volatile("cli; hlt");
|
||||
__builtin_unreachable();
|
||||
Arch::HaltForever();
|
||||
}
|
||||
|
||||
static int64_t Sys_Suspend() {
|
||||
@@ -96,8 +93,8 @@ namespace montauk::abi {
|
||||
// SYS_POWERINFO. Fills a CPU power/thermal snapshot. Returns -1 when the
|
||||
// CPU exposes no power management features (e.g. under QEMU).
|
||||
static int64_t Sys_PowerInfo(PowerInfo* out) {
|
||||
Hal::CpuPower::Snapshot s{};
|
||||
if (!Hal::CpuPower::GetSnapshot(s)) {
|
||||
Arch::CpuPower::Snapshot s{};
|
||||
if (!Arch::CpuPower::GetSnapshot(s)) {
|
||||
return NoDevice;
|
||||
}
|
||||
|
||||
@@ -112,7 +109,7 @@ namespace montauk::abi {
|
||||
out->baseMHz = s.BaseMHz;
|
||||
out->maxMHz = s.MaxMHz;
|
||||
out->effMHz = s.EffMHz;
|
||||
out->apIdleHint = Hal::CpuIdle::GetApIdleHint();
|
||||
out->apIdleHint = s.ApIdleHint;
|
||||
return Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
|
||||
@@ -6,23 +6,23 @@
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Cpu.hpp>
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
// ---- Random number generation ----
|
||||
// Uses RDTSC mixed with xorshift64* PRNG for entropy.
|
||||
// RDRAND is intentionally avoided: some firmware disables the RDRAND
|
||||
// hardware unit while CPUID still advertises support (bit 30 of ECX),
|
||||
// causing #UD on real hardware. RDTSC-based entropy is sufficient for
|
||||
// seeding BearSSL's PRNG for TLS session keys.
|
||||
// Uses the CPU timestamp counter mixed with xorshift64* PRNG for entropy.
|
||||
// RDRAND is intentionally avoided on x86: some firmware disables the
|
||||
// RDRAND hardware unit while CPUID still advertises support (bit 30 of
|
||||
// ECX), causing #UD on real hardware. Timestamp-based entropy is
|
||||
// sufficient for seeding BearSSL's PRNG for TLS session keys.
|
||||
|
||||
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
|
||||
uint64_t tsc;
|
||||
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
||||
uint64_t tsc = Arch::ReadTimestamp();
|
||||
uint64_t state = tsc;
|
||||
|
||||
for (uint64_t i = 0; i < len; i += 8) {
|
||||
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
|
||||
tsc = Arch::ReadTimestamp();
|
||||
state ^= tsc;
|
||||
state ^= state >> 12;
|
||||
state ^= state << 25;
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#include "Syscall.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
|
||||
/* For common functions used by multiple syscall implementations*/
|
||||
#include "Common.hpp"
|
||||
@@ -42,9 +40,6 @@
|
||||
#include "Clipboard.hpp" // SYS_CLIPBOARD_* (text and multi-format)
|
||||
#include "UserMemory.hpp"
|
||||
|
||||
// Assembly entry point
|
||||
extern "C" void SyscallEntry();
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
static constexpr uint64_t kMaxPrintableStringBytes = 4096;
|
||||
@@ -796,29 +791,13 @@ namespace montauk::abi {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- SYSCALL MSR initialization ----
|
||||
// ---- Entry point initialization ----
|
||||
|
||||
void InitializeSyscalls() {
|
||||
// Enable SYSCALL/SYSRET and no-execute page permissions in EFER.
|
||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
||||
efer |= 1 | (1ULL << 11); // SCE | NXE
|
||||
Hal::WriteMSR(Hal::IA32_EFER, efer);
|
||||
Arch::InitializeSyscallEntry();
|
||||
|
||||
// STAR: kernel CS in [47:32], sysret base in [63:48]
|
||||
// SYSCALL: CS=0x08, SS=0x10
|
||||
// SYSRET: CS=0x10+16=0x20|RPL3=0x23, SS=0x10+8=0x18|RPL3=0x1B
|
||||
uint64_t star = (0x0010ULL << 48) | (0x0008ULL << 32);
|
||||
Hal::WriteMSR(Hal::IA32_STAR, star);
|
||||
|
||||
// LSTAR: SYSCALL entry point
|
||||
Hal::WriteMSR(Hal::IA32_LSTAR, (uint64_t)SyscallEntry);
|
||||
|
||||
// FMASK: mask IF on SYSCALL entry (bit 9 = IF)
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
|
||||
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", "
|
||||
<< (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
|
||||
Kt::KernelLogStream(Kt::OK, "Syscall") << "System call entry installed ("
|
||||
<< kcp::dec << (SYS_SPAWN_REDIR_CAPS + 1) << " syscall slots)";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <Common/StatusCodes.hpp>
|
||||
#include <Arch/Syscall.hpp>
|
||||
|
||||
namespace montauk::abi {
|
||||
|
||||
@@ -1229,15 +1230,11 @@ namespace montauk::abi {
|
||||
uint64_t timestampTick;
|
||||
};
|
||||
|
||||
// Stack frame pushed by SyscallEntry.asm
|
||||
struct SyscallFrame {
|
||||
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
|
||||
uint64_t arg6, arg5, arg4, arg3, arg2, arg1;
|
||||
uint64_t syscall_nr;
|
||||
uint64_t user_rflags, user_rip, user_rsp;
|
||||
};
|
||||
// Saved user context of a system call; its layout is the
|
||||
// architecture's (see Arch/Syscall.hpp).
|
||||
using SyscallFrame = Arch::SyscallFrame;
|
||||
|
||||
// Kernel-only: set up SYSCALL MSRs and initialize dispatch
|
||||
// Kernel-only: install the system-call entry point and initialize dispatch
|
||||
void InitializeSyscalls();
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Timekeeping/Time.hpp>
|
||||
|
||||
#include "Syscall.hpp"
|
||||
|
||||
@@ -19,9 +19,9 @@ namespace montauk::abi::UserMemory {
|
||||
return addr != 0 && addr < USER_SPACE_END;
|
||||
}
|
||||
|
||||
inline uint64_t CurrentPml4() {
|
||||
inline uint64_t CurrentRoot() {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
return proc ? proc->pml4Phys : 0;
|
||||
return proc ? proc->pageTableRoot : 0;
|
||||
}
|
||||
|
||||
inline bool Range(uint64_t addr, uint64_t size, bool requireWrite = false) {
|
||||
@@ -30,10 +30,10 @@ namespace montauk::abi::UserMemory {
|
||||
if (addr + size < addr) return false;
|
||||
if (addr + size > USER_SPACE_END) return false;
|
||||
|
||||
uint64_t pml4Phys = CurrentPml4();
|
||||
if (pml4Phys == 0) return false;
|
||||
uint64_t pageTableRoot = CurrentRoot();
|
||||
if (pageTableRoot == 0) return false;
|
||||
|
||||
if (Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite))
|
||||
if (Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, addr, size, requireWrite))
|
||||
return true;
|
||||
|
||||
// Anonymous VMAs are demand-committed. Validation precedes the actual
|
||||
@@ -42,13 +42,13 @@ namespace montauk::abi::UserMemory {
|
||||
uint64_t page = addr & ~0xFFFULL;
|
||||
uint64_t last = (addr + size - 1) & ~0xFFFULL;
|
||||
for (;;) {
|
||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, page, 1, requireWrite) &&
|
||||
!TryHandleAnonymousPageFault(page, requireWrite ? 2ULL : 0ULL))
|
||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, page, 1, requireWrite) &&
|
||||
!TryHandleAnonymousPageFault(page, {false, requireWrite, false}))
|
||||
return false;
|
||||
if (page == last) break;
|
||||
page += 0x1000ULL;
|
||||
}
|
||||
return Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr, size, requireWrite);
|
||||
return Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, addr, size, requireWrite);
|
||||
}
|
||||
|
||||
inline bool OptionalRange(uint64_t addr, uint64_t size, bool requireWrite = false) {
|
||||
@@ -68,13 +68,13 @@ namespace montauk::abi::UserMemory {
|
||||
inline bool String(uint64_t addr, uint64_t maxLen) {
|
||||
if (!ValidUserPtr(addr) || maxLen == 0) return false;
|
||||
|
||||
uint64_t pml4Phys = CurrentPml4();
|
||||
if (pml4Phys == 0) return false;
|
||||
uint64_t pageTableRoot = CurrentRoot();
|
||||
if (pageTableRoot == 0) return false;
|
||||
|
||||
const char* str = (const char*)addr;
|
||||
for (uint64_t i = 0; i < maxLen; i++) {
|
||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pml4Phys, addr + i, 1, false)) {
|
||||
if (!TryHandleAnonymousPageFault(addr + i, 0)) return false;
|
||||
if (!Memory::VMM::Paging::IsUserRangeAccessible(pageTableRoot, addr + i, 1, false)) {
|
||||
if (!TryHandleAnonymousPageFault(addr + i, {false, false, false})) return false;
|
||||
}
|
||||
if (str[i] == '\0') return true;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,13 @@ namespace WinServer {
|
||||
Ipc::ReleaseSurface(surface);
|
||||
}
|
||||
|
||||
static int UnmapRetiredSnapshotsLocked(int windowId, int callerPid, uint64_t callerPml4) {
|
||||
static int UnmapRetiredSnapshotsLocked(int windowId, int callerPid, uint64_t callerRoot) {
|
||||
int result = InvalidArgument;
|
||||
for (int i = 0; i < MaxRetiredSnapshots; i++) {
|
||||
if (!g_retiredSnapshots[i].used || g_retiredSnapshots[i].windowId != windowId) continue;
|
||||
|
||||
if (g_retiredSnapshots[i].surface != nullptr) {
|
||||
if (Ipc::UnmapSurfaceForPid(g_retiredSnapshots[i].surface, callerPid, callerPml4) == 0) {
|
||||
if (Ipc::UnmapSurfaceForPid(g_retiredSnapshots[i].surface, callerPid, callerRoot) == 0) {
|
||||
result = Success;
|
||||
}
|
||||
Ipc::ReleaseSurface(g_retiredSnapshots[i].surface);
|
||||
@@ -84,7 +84,7 @@ namespace WinServer {
|
||||
if (unmapOwner && slot.liveSurface != nullptr) {
|
||||
auto* owner = Sched::GetProcessByPid(slot.ownerPid);
|
||||
if (owner != nullptr) {
|
||||
Ipc::UnmapSurfaceForPid(slot.liveSurface, slot.ownerPid, owner->pml4Phys);
|
||||
Ipc::UnmapSurfaceForPid(slot.liveSurface, slot.ownerPid, owner->pageTableRoot);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ namespace WinServer {
|
||||
return rc > 0 ? Success : Busy;
|
||||
}
|
||||
|
||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
||||
int Create(int ownerPid, uint64_t ownerRoot, const char* title, int w, int h,
|
||||
uint64_t& heapNext, uint64_t& outVa) {
|
||||
WsGuard guard;
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace WinServer {
|
||||
FillSurfaceLocked(snapshot, w, h, InitialWindowPixel);
|
||||
|
||||
uint64_t userVa = 0;
|
||||
if (Ipc::MapSurfaceForPid(live, ownerPid, ownerPml4, heapNext, userVa, false) != 0) {
|
||||
if (Ipc::MapSurfaceForPid(live, ownerPid, ownerRoot, heapNext, userVa, false) != 0) {
|
||||
Ipc::ReleaseMailbox(mailbox, true, true);
|
||||
Ipc::ReleaseSurface(snapshot);
|
||||
Ipc::ReleaseSurface(live);
|
||||
@@ -287,7 +287,7 @@ namespace WinServer {
|
||||
return count;
|
||||
}
|
||||
|
||||
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext) {
|
||||
uint64_t Map(int windowId, int callerPid, uint64_t callerRoot, uint64_t& heapNext) {
|
||||
WsGuard guard;
|
||||
if (windowId < 0 || windowId >= MaxWindows) return 0;
|
||||
|
||||
@@ -295,23 +295,23 @@ namespace WinServer {
|
||||
if (!slot.used || slot.snapshotSurface == nullptr) return 0;
|
||||
|
||||
uint64_t outVa = 0;
|
||||
if (Ipc::MapSurfaceForPid(slot.snapshotSurface, callerPid, callerPml4, heapNext, outVa, false) != 0) {
|
||||
if (Ipc::MapSurfaceForPid(slot.snapshotSurface, callerPid, callerRoot, heapNext, outVa, false) != 0) {
|
||||
return 0;
|
||||
}
|
||||
return outVa;
|
||||
}
|
||||
|
||||
int Unmap(int windowId, int callerPid, uint64_t callerPml4) {
|
||||
int Unmap(int windowId, int callerPid, uint64_t callerRoot) {
|
||||
WsGuard guard;
|
||||
if (windowId < 0 || windowId >= MaxWindows) {
|
||||
return UnmapRetiredSnapshotsLocked(windowId, callerPid, callerPml4);
|
||||
return UnmapRetiredSnapshotsLocked(windowId, callerPid, callerRoot);
|
||||
}
|
||||
|
||||
int result = UnmapRetiredSnapshotsLocked(windowId, callerPid, callerPml4);
|
||||
int result = UnmapRetiredSnapshotsLocked(windowId, callerPid, callerRoot);
|
||||
WindowSlot& slot = g_slots[windowId];
|
||||
if (!slot.used || slot.snapshotSurface == nullptr) return result;
|
||||
|
||||
int current = Ipc::UnmapSurfaceForPid(slot.snapshotSurface, callerPid, callerPml4);
|
||||
int current = Ipc::UnmapSurfaceForPid(slot.snapshotSurface, callerPid, callerRoot);
|
||||
if (current == 0) return Success;
|
||||
return result;
|
||||
}
|
||||
@@ -321,7 +321,7 @@ namespace WinServer {
|
||||
return SendEventLocked(windowId, event);
|
||||
}
|
||||
|
||||
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
||||
int Resize(int windowId, int callerPid, uint64_t ownerRoot, int newW, int newH,
|
||||
uint64_t& heapNext, uint64_t& outVa) {
|
||||
WsGuard guard;
|
||||
if (windowId < 0 || windowId >= MaxWindows) return InvalidArgument;
|
||||
@@ -348,7 +348,7 @@ namespace WinServer {
|
||||
Ipc::RetainSurface(newSnapshot);
|
||||
|
||||
uint64_t newOwnerVa = 0;
|
||||
if (Ipc::MapSurfaceForPid(newLive, callerPid, ownerPml4, heapNext, newOwnerVa, false) != 0) {
|
||||
if (Ipc::MapSurfaceForPid(newLive, callerPid, ownerRoot, heapNext, newOwnerVa, false) != 0) {
|
||||
Ipc::ReleaseSurface(newSnapshot);
|
||||
Ipc::ReleaseSurface(newLive);
|
||||
return GenericFailure;
|
||||
@@ -366,7 +366,7 @@ namespace WinServer {
|
||||
}
|
||||
|
||||
if (slot.liveSurface != nullptr) {
|
||||
Ipc::UnmapSurfaceForPid(slot.liveSurface, callerPid, ownerPml4);
|
||||
Ipc::UnmapSurfaceForPid(slot.liveSurface, callerPid, ownerRoot);
|
||||
Ipc::ReleaseSurface(slot.liveSurface);
|
||||
}
|
||||
if (slot.snapshotSurface != nullptr) {
|
||||
|
||||
@@ -28,16 +28,16 @@ namespace WinServer {
|
||||
uint32_t flags;
|
||||
};
|
||||
|
||||
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
|
||||
int Create(int ownerPid, uint64_t ownerRoot, const char* title, int w, int h,
|
||||
uint64_t& heapNext, uint64_t& outVa);
|
||||
int Destroy(int windowId, int callerPid);
|
||||
int Present(int windowId, int callerPid);
|
||||
int Poll(int windowId, int callerPid, montauk::abi::WinEvent* outEvent);
|
||||
int Enumerate(montauk::abi::WinInfo* outArray, int maxCount);
|
||||
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext);
|
||||
int Unmap(int windowId, int callerPid, uint64_t callerPml4);
|
||||
uint64_t Map(int windowId, int callerPid, uint64_t callerRoot, uint64_t& heapNext);
|
||||
int Unmap(int windowId, int callerPid, uint64_t callerRoot);
|
||||
int SendEvent(int windowId, const montauk::abi::WinEvent* event);
|
||||
int Resize(int windowId, int callerPid, uint64_t ownerPml4, int newW, int newH,
|
||||
int Resize(int windowId, int callerPid, uint64_t ownerRoot, int newW, int newH,
|
||||
uint64_t& heapNext, uint64_t& outVa);
|
||||
void CleanupProcess(int pid);
|
||||
int SetCursor(int windowId, int callerPid, int cursor);
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace montauk::abi {
|
||||
if (proc == nullptr) return GenericFailure;
|
||||
|
||||
uint64_t outVa = 0;
|
||||
int id = WinServer::Create(proc->pid, proc->pml4Phys, title, w, h,
|
||||
int id = WinServer::Create(proc->pid, proc->pageTableRoot, title, w, h,
|
||||
proc->heapNext, outVa);
|
||||
result->id = id;
|
||||
result->pixelVa = (id >= 0) ? outVa : 0;
|
||||
@@ -55,13 +55,13 @@ namespace montauk::abi {
|
||||
if (!Sched::HasCapability(CAP_COMPOSITOR)) return 0;
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return 0;
|
||||
return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext);
|
||||
return WinServer::Map(windowId, proc->pid, proc->pageTableRoot, proc->heapNext);
|
||||
}
|
||||
|
||||
static int Sys_WinUnmap(int windowId) {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return GenericFailure;
|
||||
return WinServer::Unmap(windowId, proc->pid, proc->pml4Phys);
|
||||
return WinServer::Unmap(windowId, proc->pid, proc->pageTableRoot);
|
||||
}
|
||||
|
||||
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
|
||||
@@ -74,7 +74,7 @@ namespace montauk::abi {
|
||||
auto* proc = Sched::GetCurrentProcessPtr();
|
||||
if (proc == nullptr) return 0;
|
||||
uint64_t outVa = 0;
|
||||
int r = WinServer::Resize(windowId, proc->pid, proc->pml4Phys, newW, newH,
|
||||
int r = WinServer::Resize(windowId, proc->pid, proc->pageTableRoot, newW, newH,
|
||||
proc->heapNext, outVa);
|
||||
return (r == 0) ? outVa : 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Abi.hpp
|
||||
* Architecture-specific parts of the user-mode ABI
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Arch.hpp>
|
||||
|
||||
// What the ELF loader and the process/thread setup code need to know about
|
||||
// the user-mode binary interface of the target. Defined inline by
|
||||
// Arch/<arch>/Abi.hpp.
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// ELF e_machine value accepted for user programs and libraries, and a
|
||||
// name for diagnostics.
|
||||
// constexpr uint16_t ElfMachine;
|
||||
// constexpr const char* ElfMachineName;
|
||||
|
||||
// Calling-convention attribute for UEFI runtime services:
|
||||
// #define ARCH_EFIAPI
|
||||
|
||||
// How the loader must treat a dynamic relocation type.
|
||||
enum class RelocationKind {
|
||||
None, // no-op (type 0)
|
||||
Relative, // *target = loadBias + addend
|
||||
Symbol, // *target = symbol address + addend (64-bit absolute)
|
||||
Unsupported,
|
||||
};
|
||||
inline RelocationKind ClassifyRelocation(uint32_t type);
|
||||
|
||||
// Geometry of one thread's TLS block, as offsets from its page-aligned
|
||||
// base. The variant (I: TLS above the thread pointer, II: below it) is
|
||||
// fixed by the architecture's ELF TLS ABI.
|
||||
struct TlsLayout {
|
||||
uint64_t templateOffset; // where .tdata/.tbss start
|
||||
uint64_t tpOffset; // thread pointer; [TP] holds the self-pointer
|
||||
uint64_t totalSize; // bytes to map, incl. the TCB
|
||||
};
|
||||
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign);
|
||||
|
||||
// Emit the user-mode code that calls SYS_EXIT(0). A process's entry
|
||||
// function returns into it.
|
||||
inline void WriteExitStub(uint8_t* page);
|
||||
|
||||
// Prepare the main thread's initial user stack so that returning from
|
||||
// the entry function lands at `returnAddress`. `topPage` is the kernel
|
||||
// mapping of the page just below `stackTop`. Returns the initial user SP.
|
||||
inline uint64_t SetupUserEntryStack(uint8_t* topPage, uint64_t stackTop,
|
||||
uint64_t returnAddress);
|
||||
|
||||
// Initial user SP for a sibling thread whose stack ends at `stackTop`
|
||||
// (the thread trampoline never returns, so no return address is set).
|
||||
inline uint64_t ThreadEntryStackPointer(uint64_t stackTop);
|
||||
|
||||
}
|
||||
|
||||
#if MONTAUK_ARCH_X86_64
|
||||
#include <Arch/x86_64/Abi.hpp>
|
||||
#elif MONTAUK_ARCH_AARCH64
|
||||
#include <Arch/aarch64/Abi.hpp>
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Arch.hpp
|
||||
* Target architecture selection
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Everything outside src/Arch/ is architecture-neutral and talks to the CPU
|
||||
// only through the interfaces in src/Arch/*.hpp. Each supported architecture
|
||||
// implements them under src/Arch/<arch>/; the build compiles only the
|
||||
// directory matching ARCH. See docs/porting.md.
|
||||
//
|
||||
// Interface headers that need inline (per-arch) definitions include the
|
||||
// matching implementation header at their end, selected below.
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#define MONTAUK_ARCH_X86_64 1
|
||||
#define MONTAUK_ARCH_NAME "x86_64"
|
||||
#elif defined(__aarch64__)
|
||||
#define MONTAUK_ARCH_AARCH64 1
|
||||
#define MONTAUK_ARCH_NAME "aarch64"
|
||||
#else
|
||||
#error "MontaukOS: unsupported target architecture (add one under src/Arch/)"
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Context.hpp
|
||||
* Architecture-neutral thread context interface
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Arch.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
|
||||
// The scheduler's view of a thread's CPU context: a kernel stack pointer
|
||||
// saved by SwitchContext, an FPU/SIMD save area, and a page-table root.
|
||||
|
||||
#if MONTAUK_ARCH_X86_64
|
||||
#include <Arch/x86_64/Context.hpp>
|
||||
#elif MONTAUK_ARCH_AARCH64
|
||||
#include <Arch/aarch64/Context.hpp>
|
||||
#endif
|
||||
|
||||
// The architecture provides (in Arch/<arch>/Context.hpp):
|
||||
// constexpr uint64_t Arch::FpuStateSize, Arch::FpuStateAlign
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Reset an FPU/SIMD save area to the state a new thread starts with.
|
||||
void InitFpuState(uint8_t* area);
|
||||
|
||||
// Build the initial kernel stack of a new thread so that the first
|
||||
// SwitchContext to it "returns" into `entry`. Returns the saved SP.
|
||||
uint64_t PrepareThreadStack(uint64_t kernelStackTop, void (*entry)());
|
||||
|
||||
// Save the running context (stack pointer to *oldSp, FPU state to
|
||||
// oldFpu when non-null), switch to page-table root `newRoot` if it is
|
||||
// not already active, and resume the context saved at newSp (restoring
|
||||
// newFpu when non-null). Returns when the old context is resumed.
|
||||
void SwitchContext(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||
uint8_t* oldFpu, uint8_t* newFpu);
|
||||
|
||||
// Point this CPU at the thread about to run: the kernel stack used when
|
||||
// it enters the kernel from user mode, and its TLS thread pointer.
|
||||
void ActivateThread(Smp::CpuData* cpu, uint64_t kernelStackTop,
|
||||
uint64_t threadPointer);
|
||||
|
||||
// Drop to user mode at `entry` with stack `userSp`; `arg` is the first
|
||||
// argument register. Interrupts are enabled in user mode.
|
||||
[[noreturn]] void EnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Cpu.hpp
|
||||
* Architecture-neutral CPU primitives
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Arch.hpp>
|
||||
|
||||
// Every function here is defined inline by the architecture
|
||||
// (Arch/<arch>/Cpu.hpp), so the primitives cost what the raw instruction
|
||||
// costs. Generic kernel code must use these instead of inline assembly.
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Saved interrupt-enable state, as returned by SaveAndDisableInterrupts.
|
||||
// Opaque to generic code: only hand it back to RestoreInterrupts.
|
||||
using IrqFlags = uint64_t;
|
||||
|
||||
// ---- Spinning and barriers ----
|
||||
|
||||
// Spin-loop hint (x86 PAUSE, ARM YIELD).
|
||||
inline void Pause();
|
||||
|
||||
// Stop the compiler from caching memory across this point. Emits no
|
||||
// instruction; use it in polling loops on device-written memory.
|
||||
inline void CompilerBarrier() {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
|
||||
// Order all earlier stores before all later stores, including stores to
|
||||
// write-combining memory (framebuffers) that must become visible.
|
||||
inline void StoreFence();
|
||||
|
||||
// Full read/write memory barrier.
|
||||
inline void MemoryFence();
|
||||
|
||||
// ---- Interrupt state ----
|
||||
|
||||
inline void EnableInterrupts();
|
||||
inline void DisableInterrupts();
|
||||
inline bool InterruptsEnabled();
|
||||
|
||||
// Disable interrupts and return the previous state.
|
||||
inline IrqFlags SaveAndDisableInterrupts();
|
||||
|
||||
// Restore an interrupt state returned by SaveAndDisableInterrupts.
|
||||
inline void RestoreInterrupts(IrqFlags flags);
|
||||
|
||||
// ---- Halting ----
|
||||
|
||||
// Wait for the next interrupt. Interrupt state is left as it was; with
|
||||
// interrupts disabled this only returns on an NMI-class event.
|
||||
inline void Halt();
|
||||
|
||||
// Called with interrupts disabled: atomically enable them and wait for
|
||||
// an interrupt, then disable them again before returning. The atomicity
|
||||
// closes the race where a wakeup IRQ lands between the readiness check
|
||||
// and the wait, leaving the CPU asleep until the next timer deadline.
|
||||
inline void HaltWithInterruptsDisabled();
|
||||
|
||||
// Disable interrupts and stop this CPU for good.
|
||||
[[noreturn]] inline void HaltForever();
|
||||
|
||||
// ---- Time and delays ----
|
||||
|
||||
// A free-running, monotonically increasing cycle counter (x86 TSC, ARM
|
||||
// CNTVCT). Its rate is architecture-defined; use it for entropy and
|
||||
// relative timing, not wall-clock time.
|
||||
inline uint64_t ReadTimestamp();
|
||||
|
||||
// Delay for at least about one microsecond. Works with interrupts
|
||||
// disabled and before any clock is calibrated; errs long.
|
||||
inline void IoDelay();
|
||||
|
||||
// ---- Caches and translation ----
|
||||
|
||||
// Write back and invalidate all CPU caches (before power state changes).
|
||||
inline void WritebackInvalidateCaches();
|
||||
|
||||
// Invalidate this CPU's translation for one virtual page.
|
||||
inline void FlushTlbPage(uint64_t virtualAddress);
|
||||
|
||||
// Invalidate this CPU's non-global translations.
|
||||
inline void FlushTlbAll();
|
||||
|
||||
// Physical address of the active page-table root (x86 CR3, ARM TTBR0).
|
||||
inline uint64_t ReadPageTableRoot();
|
||||
|
||||
// Switch to the page-table root at the given physical address.
|
||||
inline void WritePageTableRoot(uint64_t rootPhys);
|
||||
|
||||
}
|
||||
|
||||
#if MONTAUK_ARCH_X86_64
|
||||
#include <Arch/x86_64/Cpu.hpp>
|
||||
#elif MONTAUK_ARCH_AARCH64
|
||||
#include <Arch/aarch64/Cpu.hpp>
|
||||
#endif
|
||||
@@ -1,14 +1,18 @@
|
||||
/*
|
||||
* CpuPower.hpp
|
||||
* Intel CPU power management: HWP frequency scaling, C1E promotion,
|
||||
* and the package thermal governor.
|
||||
* Architecture-neutral CPU power/thermal management interface
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hal {
|
||||
// Frequency scaling, idle-state policy and the thermal governor are CPU
|
||||
// specific (x86_64: Intel HWP, C1E promotion and a package-temperature
|
||||
// governor driven through MSRs). An architecture without them implements
|
||||
// these as no-ops and returns false from GetSnapshot.
|
||||
|
||||
namespace Arch {
|
||||
namespace CpuPower {
|
||||
|
||||
// Snapshot of the power/thermal state for SYS_POWERINFO and logging.
|
||||
@@ -24,6 +28,7 @@ namespace Hal {
|
||||
uint32_t BaseMHz; // CPUID base frequency (0 = unknown)
|
||||
uint32_t MaxMHz; // CPUID max turbo frequency
|
||||
uint32_t EffMHz; // measured average active frequency (BSP)
|
||||
uint8_t ApIdleHint; // idle state secondary CPUs enter (arch-defined)
|
||||
};
|
||||
|
||||
// BSP: detect features, enable HWP with a balanced energy preference,
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Interrupts.hpp
|
||||
* Architecture-neutral hardware interrupt interface
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// Devices and kernel services see interrupts as numbered IRQ slots. The
|
||||
// architecture maps each slot onto its interrupt controller (x86: an IDT
|
||||
// vector fed by the I/O APIC, MSIs and local APIC IPIs; ARM: a GIC INTID)
|
||||
// and calls the registered handler.
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// IRQ handler function type. The parameter is the IRQ slot number.
|
||||
// `fromUser` is derived directly from the interrupted context. Do not
|
||||
// cache it in per-CPU data: an IRQ handler may context-switch and later
|
||||
// resume on another CPU, making per-CPU saved-origin state stale.
|
||||
using IrqHandler = void(*)(uint8_t irq, bool fromUser);
|
||||
|
||||
// Number of IRQ slots: 0-23 platform interrupt lines, 24-45 message-
|
||||
// signalled interrupts (one slot per MSI-capable driver), 46-47 IPIs.
|
||||
constexpr int IRQ_COUNT = 48;
|
||||
|
||||
// The architecture's periodic scheduler/timekeeping timer.
|
||||
constexpr uint8_t IRQ_TIMER = 0;
|
||||
|
||||
// Platform interrupt lines with PC/ISA meaning. Only drivers for PC
|
||||
// devices (PS/2, legacy PCI INTx through the I/O APIC) use these.
|
||||
constexpr uint8_t IRQ_KEYBOARD = 1;
|
||||
constexpr uint8_t IRQ_CASCADE = 2;
|
||||
constexpr uint8_t IRQ_COM2 = 3;
|
||||
constexpr uint8_t IRQ_COM1 = 4;
|
||||
constexpr uint8_t IRQ_FLOPPY = 6;
|
||||
constexpr uint8_t IRQ_RTC = 8;
|
||||
constexpr uint8_t IRQ_MOUSE = 12;
|
||||
constexpr uint8_t IRQ_ATA1 = 14;
|
||||
constexpr uint8_t IRQ_ATA2 = 15;
|
||||
|
||||
// Inter-processor interrupts.
|
||||
constexpr uint8_t IRQ_TLB_SHOOTDOWN = 46;
|
||||
constexpr uint8_t IRQ_RESCHEDULE = 47;
|
||||
|
||||
// Register the handler for an IRQ slot.
|
||||
void RegisterIrqHandler(uint8_t irq, IrqHandler handler);
|
||||
|
||||
// Route platform interrupt line `irq` to IRQ slot `irq` on the boot CPU,
|
||||
// applying the firmware's polarity/trigger overrides, and unmask it.
|
||||
void RouteLegacyIrq(uint8_t irq);
|
||||
|
||||
// Unmask platform interrupt line `irq` (already routed at boot).
|
||||
void EnableLegacyIrq(uint8_t irq);
|
||||
|
||||
// A message-signalled interrupt targeting IRQ slot `irq` on the boot
|
||||
// CPU: the device writes `data` to `address` to raise it. Program these
|
||||
// into a PCI MSI capability or MSI-X table entry. Returns false when the
|
||||
// platform cannot receive MSIs (drivers then fall back to INTx/polling).
|
||||
struct MsiMessage {
|
||||
uint64_t address;
|
||||
uint32_t data;
|
||||
};
|
||||
bool ComposeMsi(uint8_t irq, MsiMessage& out);
|
||||
|
||||
// Raise IPI slot `irq` (IRQ_RESCHEDULE / IRQ_TLB_SHOOTDOWN) on the CPU
|
||||
// with the given Smp index. Returns false if delivery did not complete.
|
||||
bool SendIpi(int cpuIndex, uint8_t irq);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Platform.hpp
|
||||
* Architecture hooks for kernel bring-up and power-off paths
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <ACPI/ACPI.hpp>
|
||||
|
||||
// kmain (Boot/Main.cpp) owns the boot sequence and calls into the
|
||||
// architecture at these points, in this order.
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// First thing after the boot console exists: CPU descriptor tables,
|
||||
// FPU/SIMD enablement. No memory allocation is available yet.
|
||||
void EarlyInitialize();
|
||||
|
||||
// The frame allocator and heap exist: install exception/trap vectors.
|
||||
void InitializeTraps();
|
||||
|
||||
// The kernel page tables are live: program memory-type attributes that
|
||||
// MapWC/MapMMIO rely on (x86 PAT).
|
||||
void InitializeMemoryAttributes();
|
||||
|
||||
// ACPI tables are available: bring up interrupt controllers, the boot
|
||||
// CPU's per-CPU data (Smp::InitBsp), idle-state and power management.
|
||||
// Interrupts stay disabled; kmain enables them afterwards.
|
||||
void InitializeInterrupts(Hal::ACPI::CommonSDTHeader* xsdt);
|
||||
|
||||
// Platform devices that are not discovered on PCI (x86: the PS/2
|
||||
// controller, keyboard and mouse). Called once the timer is running.
|
||||
void InitializePlatformDevices();
|
||||
|
||||
// Right before the first user process is created: anything user-mode
|
||||
// entry needs (x86: load the task register for ring-3 interrupt stacks).
|
||||
void PrepareUserMode();
|
||||
|
||||
// Secondary CPUs are up and the first process is about to be spawned.
|
||||
void LateInitialize();
|
||||
|
||||
// Report devices that belong to the platform rather than a bus (for
|
||||
// SYS_DEVLIST): interrupt controllers (category 1), timers (2) and
|
||||
// platform input devices (3).
|
||||
using PlatformDeviceSink = void (*)(void* context, uint8_t category,
|
||||
const char* name, const char* detail);
|
||||
void ListPlatformDevices(PlatformDeviceSink sink, void* context);
|
||||
|
||||
// Reset the machine without firmware help (last resort after EFI
|
||||
// ResetSystem). Never returns.
|
||||
[[noreturn]] void Reboot();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Smp.hpp
|
||||
* Architecture-neutral per-CPU data and SMP bring-up interface
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Arch.hpp>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
|
||||
#if MONTAUK_ARCH_X86_64
|
||||
#include <Arch/x86_64/PerCpu.hpp>
|
||||
#elif MONTAUK_ARCH_AARCH64
|
||||
#include <Arch/aarch64/PerCpu.hpp>
|
||||
#endif
|
||||
|
||||
// The architecture provides (in Arch/<arch>/PerCpu.hpp):
|
||||
// struct Arch::CpuEntryState -- fields kernel-entry assembly reads at a
|
||||
// fixed offset (8) from the per-CPU base
|
||||
// struct Arch::CpuState -- everything else the port keeps per CPU
|
||||
// void* Arch::CurrentCpuLocal() -- this CPU's CpuData (fast path)
|
||||
// void* Arch::TryCurrentCpuLocal() -- same, or nullptr before InitBsp
|
||||
|
||||
namespace Smp {
|
||||
|
||||
static constexpr int MaxCPUs = 64;
|
||||
|
||||
struct CpuData {
|
||||
// === Fixed layout: read by low-level entry code ===
|
||||
uint64_t selfPtr; // offset 0: pointer to self
|
||||
Arch::CpuEntryState entry; // offset 8: arch kernel-entry fields
|
||||
|
||||
// === Generic fields ===
|
||||
int32_t currentSlot; // process table slot, -1 = idle
|
||||
int cpuIndex; // 0-based CPU index (0 = boot CPU)
|
||||
uint64_t idleSavedSp; // idle context's stack pointer while a process runs
|
||||
volatile bool started; // set by an AP once its init is complete
|
||||
|
||||
// When set, the scheduler tick must NOT pull this (idle) CPU away to
|
||||
// run a ready process: its idle context is busy with long-running
|
||||
// kernel work whose wall-clock-bounded waits would spuriously expire
|
||||
// if the context only ran when nothing else is ready (observed: the
|
||||
// deferred Bluetooth firmware download racing login-screen startup).
|
||||
// Other CPUs schedule normally while this is held.
|
||||
volatile bool reservedForKernelWork = false;
|
||||
|
||||
// Set by the BSP timer when an IRQ bottom half needs process-safe
|
||||
// execution even though runnable user processes keep this CPU busy.
|
||||
// Schedule() consumes it by yielding once to the idle kernel context.
|
||||
bool kernelWorkPending = false;
|
||||
|
||||
// === Architecture-private state ===
|
||||
Arch::CpuState arch;
|
||||
};
|
||||
|
||||
static_assert(__builtin_offsetof(CpuData, selfPtr) == 0,
|
||||
"CpuData: per-CPU base must point at selfPtr");
|
||||
static_assert(__builtin_offsetof(CpuData, entry) == 8,
|
||||
"CpuData: entry-assembly fields must start at offset 8");
|
||||
|
||||
// The current CPU's data.
|
||||
inline CpuData* GetCurrentCpuData() {
|
||||
return (CpuData*)Arch::CurrentCpuLocal();
|
||||
}
|
||||
|
||||
// Exception paths may run before InitBsp() has installed the per-CPU
|
||||
// base. This variant returns nullptr in that state instead of faulting
|
||||
// recursively on a bogus base.
|
||||
inline CpuData* TryGetCurrentCpuData() {
|
||||
return (CpuData*)Arch::TryCurrentCpuLocal();
|
||||
}
|
||||
|
||||
// Get CPU data by index
|
||||
CpuData* GetCpuData(int index);
|
||||
|
||||
// Number of online CPUs
|
||||
int GetCpuCount();
|
||||
|
||||
// Number of CPUs the firmware describes (available once interrupts are
|
||||
// initialized; may exceed GetCpuCount if some failed to start).
|
||||
// Implemented by the architecture.
|
||||
int GetDetectedCpuCount();
|
||||
|
||||
// Initialize the boot CPU's per-CPU data (call before interrupts are
|
||||
// enabled). Implemented by the architecture.
|
||||
void InitBsp();
|
||||
|
||||
// Boot all secondary CPUs (call after all subsystems are ready).
|
||||
// `smp` is the boot contract's SMP block; if FeatureSmp was not
|
||||
// advertised the caller may still pass it (cpuCount<=1 is a no-op).
|
||||
// Implemented by the architecture.
|
||||
void BootAPs(const montauk::boot::SmpInfo& smp);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Syscall.hpp
|
||||
* Architecture-neutral system-call entry interface
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Arch.hpp>
|
||||
|
||||
#if MONTAUK_ARCH_X86_64
|
||||
#include <Arch/x86_64/SyscallFrame.hpp>
|
||||
#elif MONTAUK_ARCH_AARCH64
|
||||
#include <Arch/aarch64/SyscallFrame.hpp>
|
||||
#endif
|
||||
|
||||
// The architecture's system-call entry code saves the user context into an
|
||||
// Arch::SyscallFrame on the thread's kernel stack (Arch/<arch>/
|
||||
// SyscallFrame.hpp defines it, exposing at least `syscall_nr` and `arg1` ..
|
||||
// `arg6`), enables interrupts, and calls
|
||||
//
|
||||
// extern "C" int64_t SyscallDispatch(Arch::SyscallFrame* frame);
|
||||
//
|
||||
// whose result it returns to user mode. Before returning it calls
|
||||
//
|
||||
// extern "C" void SchedExitIfKilled();
|
||||
//
|
||||
// so a kill that arrived during the syscall takes effect at a point where
|
||||
// the syscall holds no locks.
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Install the system-call entry point on the calling CPU. Called on the
|
||||
// boot CPU by montauk::abi::InitializeSyscalls and by every secondary
|
||||
// CPU during its bring-up.
|
||||
void InitializeSyscallEntry();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Timer.hpp
|
||||
* Architecture-neutral per-CPU tick timer interface
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// The hardware behind Timekeeping (Timekeeping/Timer.hpp): a per-CPU timer
|
||||
// that raises Arch::IRQ_TIMER periodically. Timekeeping registers the IRQ
|
||||
// handler and owns all tick accounting; the architecture only calibrates
|
||||
// and programs the hardware (x86: local APIC timer calibrated against the
|
||||
// PIT; ARM: the generic timer).
|
||||
|
||||
namespace Arch::Timer {
|
||||
|
||||
// Calibrate the timer and start the calling (boot) CPU's periodic tick
|
||||
// with the given period. Returns false if no usable timer was found.
|
||||
bool InitializeBsp(uint32_t periodMs);
|
||||
|
||||
// (Re)start the calling CPU's periodic tick, reusing the boot CPU's
|
||||
// calibration: secondary CPU bring-up, leaving idle, and the boot CPU
|
||||
// after S3 resume (timer state does not survive suspend). Only called
|
||||
// after InitializeBsp succeeded.
|
||||
void StartPeriodic(uint32_t periodMs);
|
||||
|
||||
// Mask the calling CPU's tick (idle secondary CPUs rely on IPIs).
|
||||
// Cheap when it is already masked.
|
||||
void Stop();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Abi.hpp
|
||||
* aarch64 user-mode ABI details (implements Arch/Abi.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// UEFI on AArch64 uses the standard AAPCS64 calling convention.
|
||||
#define ARCH_EFIAPI
|
||||
|
||||
namespace Arch {
|
||||
|
||||
constexpr uint16_t ElfMachine = 183; // EM_AARCH64
|
||||
constexpr const char* ElfMachineName = "aarch64";
|
||||
|
||||
inline RelocationKind ClassifyRelocation(uint32_t type) {
|
||||
switch (type) {
|
||||
case 0: return RelocationKind::None;
|
||||
case 1027: return RelocationKind::Relative; // R_AARCH64_RELATIVE
|
||||
case 257: // R_AARCH64_ABS64
|
||||
case 1025: // R_AARCH64_GLOB_DAT
|
||||
case 1026: return RelocationKind::Symbol; // R_AARCH64_JUMP_SLOT
|
||||
default: return RelocationKind::Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
// Variant I: TP points at a 16-byte TCB and the TLS block follows it,
|
||||
// starting at the next p_align boundary.
|
||||
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign) {
|
||||
uint64_t align = pAlign ? pAlign : 1;
|
||||
uint64_t templateOffset = (16 + align - 1) & ~(align - 1);
|
||||
return {templateOffset, 0, templateOffset + memSize};
|
||||
}
|
||||
|
||||
// System calls: number in x8, arguments in x0-x5, `svc #0`.
|
||||
inline void WriteExitStub(uint8_t* page) {
|
||||
uint32_t* code = (uint32_t*)page;
|
||||
code[0] = 0xD2800000; // mov x0, #0 (exit code 0)
|
||||
code[1] = 0xD2800008; // mov x8, #0 (SYS_EXIT = 0)
|
||||
code[2] = 0xD4000001; // svc #0
|
||||
}
|
||||
|
||||
// The return address travels in x30, not on the stack: EnterUserMode
|
||||
// points x30 at the exit stub. The stack only needs 16-byte alignment.
|
||||
inline uint64_t SetupUserEntryStack(uint8_t*, uint64_t stackTop, uint64_t) {
|
||||
return stackTop & ~0xFULL;
|
||||
}
|
||||
|
||||
inline uint64_t ThreadEntryStackPointer(uint64_t stackTop) {
|
||||
return stackTop & ~0xFULL;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* AcpiSleep.cpp
|
||||
* aarch64: no ACPI S3 (implements ACPI/AcpiSleep.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <ACPI/AcpiSleep.hpp>
|
||||
#include <Common/StatusCodes.hpp>
|
||||
|
||||
// Suspend on ARM goes through PSCI (SYSTEM_SUSPEND), not the FACS waking
|
||||
// vector; until that exists, S3 is reported as unavailable.
|
||||
|
||||
namespace Hal::AcpiSleep {
|
||||
|
||||
void Initialize(ACPI::CommonSDTHeader*) {
|
||||
}
|
||||
|
||||
bool IsS3Available() {
|
||||
return false;
|
||||
}
|
||||
|
||||
int Suspend() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Context.S
|
||||
aarch64 context switch and first entry to EL0 (see Arch/Context.hpp)
|
||||
Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
/* The kernel is built -mgeneral-regs-only; this file alone touches
|
||||
the FP/SIMD registers, to save and restore user state. */
|
||||
.arch_extension fp
|
||||
.arch_extension simd
|
||||
|
||||
.text
|
||||
|
||||
/*
|
||||
* void ArchContextSwitch(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||
* uint8_t* oldFpu, uint8_t* newFpu)
|
||||
* x0 = where to save the old SP x1 = SP to resume
|
||||
* x2 = TTBR0 root for the new side x3/x4 = FP/SIMD save areas (may be 0)
|
||||
*
|
||||
* Frame on the kernel stack (Context.cpp's PrepareThreadStack builds the
|
||||
* same shape): x19..x28, x29, x30 -- 96 bytes. The kernel is built with
|
||||
* -mgeneral-regs-only, so q0-q31/FPCR/FPSR only ever hold user state.
|
||||
*/
|
||||
.global ArchContextSwitch
|
||||
.type ArchContextSwitch, %function
|
||||
ArchContextSwitch:
|
||||
cbz x3, 1f
|
||||
stp q0, q1, [x3, #0]
|
||||
stp q2, q3, [x3, #32]
|
||||
stp q4, q5, [x3, #64]
|
||||
stp q6, q7, [x3, #96]
|
||||
stp q8, q9, [x3, #128]
|
||||
stp q10, q11, [x3, #160]
|
||||
stp q12, q13, [x3, #192]
|
||||
stp q14, q15, [x3, #224]
|
||||
stp q16, q17, [x3, #256]
|
||||
stp q18, q19, [x3, #288]
|
||||
stp q20, q21, [x3, #320]
|
||||
stp q22, q23, [x3, #352]
|
||||
stp q24, q25, [x3, #384]
|
||||
stp q26, q27, [x3, #416]
|
||||
stp q28, q29, [x3, #448]
|
||||
stp q30, q31, [x3, #480]
|
||||
mrs x9, fpcr
|
||||
mrs x10, fpsr
|
||||
add x11, x3, #512
|
||||
stp x9, x10, [x11]
|
||||
1:
|
||||
sub sp, sp, #96
|
||||
stp x19, x20, [sp, #0]
|
||||
stp x21, x22, [sp, #16]
|
||||
stp x23, x24, [sp, #32]
|
||||
stp x25, x26, [sp, #48]
|
||||
stp x27, x28, [sp, #64]
|
||||
stp x29, x30, [sp, #80]
|
||||
mov x9, sp
|
||||
str x9, [x0]
|
||||
|
||||
mov sp, x1
|
||||
|
||||
/* Switch the process half only when it differs (no ASIDs yet, so a
|
||||
switch invalidates this CPU's TLB). */
|
||||
mrs x9, ttbr0_el1
|
||||
and x9, x9, #0x0000fffffffffffe
|
||||
cmp x9, x2
|
||||
b.eq 2f
|
||||
dsb ishst
|
||||
msr ttbr0_el1, x2
|
||||
isb
|
||||
tlbi vmalle1
|
||||
dsb nsh
|
||||
isb
|
||||
2:
|
||||
ldp x19, x20, [sp, #0]
|
||||
ldp x21, x22, [sp, #16]
|
||||
ldp x23, x24, [sp, #32]
|
||||
ldp x25, x26, [sp, #48]
|
||||
ldp x27, x28, [sp, #64]
|
||||
ldp x29, x30, [sp, #80]
|
||||
add sp, sp, #96
|
||||
|
||||
cbz x4, 3f
|
||||
ldp q0, q1, [x4, #0]
|
||||
ldp q2, q3, [x4, #32]
|
||||
ldp q4, q5, [x4, #64]
|
||||
ldp q6, q7, [x4, #96]
|
||||
ldp q8, q9, [x4, #128]
|
||||
ldp q10, q11, [x4, #160]
|
||||
ldp q12, q13, [x4, #192]
|
||||
ldp q14, q15, [x4, #224]
|
||||
ldp q16, q17, [x4, #256]
|
||||
ldp q18, q19, [x4, #288]
|
||||
ldp q20, q21, [x4, #320]
|
||||
ldp q22, q23, [x4, #352]
|
||||
ldp q24, q25, [x4, #384]
|
||||
ldp q26, q27, [x4, #416]
|
||||
ldp q28, q29, [x4, #448]
|
||||
ldp q30, q31, [x4, #480]
|
||||
add x11, x4, #512
|
||||
ldp x9, x10, [x11]
|
||||
msr fpcr, x9
|
||||
msr fpsr, x10
|
||||
3:
|
||||
ret
|
||||
.size ArchContextSwitch, . - ArchContextSwitch
|
||||
|
||||
/*
|
||||
* void ArchEnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg,
|
||||
* uint64_t returnAddress, uint64_t kernelStackTop)
|
||||
*
|
||||
* Drops to EL0t with interrupts unmasked. The kernel stack is reset to its
|
||||
* top: SP_EL1 is what the next exception from EL0 runs on.
|
||||
*/
|
||||
.global ArchEnterUserMode
|
||||
.type ArchEnterUserMode, %function
|
||||
ArchEnterUserMode:
|
||||
msr daifset, #0xf
|
||||
mov sp, x4
|
||||
msr elr_el1, x0
|
||||
msr sp_el0, x1
|
||||
msr spsr_el1, xzr /* EL0t, DAIF clear */
|
||||
mov x0, x2
|
||||
mov x30, x3
|
||||
mov x1, xzr
|
||||
mov x2, xzr
|
||||
mov x3, xzr
|
||||
mov x4, xzr
|
||||
mov x5, xzr
|
||||
mov x6, xzr
|
||||
mov x7, xzr
|
||||
mov x8, xzr
|
||||
mov x9, xzr
|
||||
mov x10, xzr
|
||||
mov x11, xzr
|
||||
mov x12, xzr
|
||||
mov x13, xzr
|
||||
mov x14, xzr
|
||||
mov x15, xzr
|
||||
mov x16, xzr
|
||||
mov x17, xzr
|
||||
mov x18, xzr
|
||||
mov x29, xzr
|
||||
eret
|
||||
.size ArchEnterUserMode, . - ArchEnterUserMode
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Context.cpp
|
||||
* aarch64 thread context (implements Arch/Context.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Context.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
|
||||
// Context.S
|
||||
extern "C" void ArchContextSwitch(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||
uint8_t* oldFpu, uint8_t* newFpu);
|
||||
extern "C" [[noreturn]] void ArchEnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg,
|
||||
uint64_t returnAddress, uint64_t kernelStackTop);
|
||||
|
||||
namespace Arch {
|
||||
|
||||
void InitFpuState(uint8_t* area) {
|
||||
// All-zero is the architectural reset state: round-to-nearest, no
|
||||
// exceptions trapped, no flags raised.
|
||||
memset(area, 0, FpuStateSize);
|
||||
}
|
||||
|
||||
uint64_t PrepareThreadStack(uint64_t kernelStackTop, void (*entry)()) {
|
||||
// The frame Context.S pops: x19..x28, x29, x30 (= return address).
|
||||
uint64_t* sp = (uint64_t*)kernelStackTop - 12;
|
||||
for (int i = 0; i < 11; i++) {
|
||||
sp[i] = 0;
|
||||
}
|
||||
sp[11] = (uint64_t)entry;
|
||||
return (uint64_t)sp;
|
||||
}
|
||||
|
||||
void SwitchContext(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||
uint8_t* oldFpu, uint8_t* newFpu) {
|
||||
ArchContextSwitch(oldSp, newSp, newRoot, oldFpu, newFpu);
|
||||
}
|
||||
|
||||
void ActivateThread(Smp::CpuData* cpu, uint64_t kernelStackTop,
|
||||
uint64_t threadPointer) {
|
||||
cpu->entry.kernelStackTop = kernelStackTop;
|
||||
asm volatile("msr tpidr_el0, %0" :: "r"(threadPointer));
|
||||
}
|
||||
|
||||
void EnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg) {
|
||||
// Returning from the entry function lands in the exit stub (see
|
||||
// SetupUserEntryStack in Abi.hpp).
|
||||
ArchEnterUserMode(entry, userSp, arg, Sched::ExitStubAddr,
|
||||
Smp::GetCurrentCpuData()->entry.kernelStackTop);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Context.hpp
|
||||
* aarch64 thread context constants (see Arch/Context.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
// q0-q31 followed by FPCR and FPSR (Context.S).
|
||||
constexpr uint64_t FpuStateSize = 32 * 16 + 16;
|
||||
constexpr uint64_t FpuStateAlign = 16;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Cpu.hpp
|
||||
* aarch64 implementation of the Arch/Cpu.hpp primitives
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// PSTATE.I in the DAIF view
|
||||
static constexpr uint64_t DaifIrqMask = 1ULL << 7;
|
||||
|
||||
inline void Pause() {
|
||||
asm volatile("yield" ::: "memory");
|
||||
}
|
||||
|
||||
// Device-nGnRE / Normal-NC (write-combining) stores complete in order
|
||||
// once a DSB has been issued.
|
||||
inline void StoreFence() {
|
||||
asm volatile("dsb st" ::: "memory");
|
||||
}
|
||||
|
||||
inline void MemoryFence() {
|
||||
asm volatile("dsb sy" ::: "memory");
|
||||
}
|
||||
|
||||
inline void EnableInterrupts() {
|
||||
asm volatile("msr daifclr, #2" ::: "memory");
|
||||
}
|
||||
|
||||
inline void DisableInterrupts() {
|
||||
asm volatile("msr daifset, #2" ::: "memory");
|
||||
}
|
||||
|
||||
inline bool InterruptsEnabled() {
|
||||
uint64_t daif;
|
||||
asm volatile("mrs %0, daif" : "=r"(daif));
|
||||
return (daif & DaifIrqMask) == 0;
|
||||
}
|
||||
|
||||
inline IrqFlags SaveAndDisableInterrupts() {
|
||||
uint64_t daif;
|
||||
asm volatile("mrs %0, daif\n\tmsr daifset, #2" : "=r"(daif) :: "memory");
|
||||
return daif;
|
||||
}
|
||||
|
||||
inline void RestoreInterrupts(IrqFlags flags) {
|
||||
asm volatile("msr daif, %0" :: "r"(flags) : "memory");
|
||||
}
|
||||
|
||||
inline void Halt() {
|
||||
asm volatile("wfi" ::: "memory");
|
||||
}
|
||||
|
||||
// WFI wakes on a pending interrupt even while PSTATE.I masks it, so
|
||||
// checking for work and then waiting with IRQs masked cannot lose a
|
||||
// wakeup. Unmask briefly afterwards so the pending IRQ is taken.
|
||||
inline void HaltWithInterruptsDisabled() {
|
||||
asm volatile("wfi\n\tmsr daifclr, #2\n\tisb\n\tmsr daifset, #2" ::: "memory");
|
||||
}
|
||||
|
||||
[[noreturn]] inline void HaltForever() {
|
||||
for (;;) {
|
||||
asm volatile("msr daifset, #0xf\n\twfi" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
inline uint64_t ReadTimestamp() {
|
||||
uint64_t value;
|
||||
asm volatile("isb\n\tmrs %0, cntvct_el0" : "=r"(value) :: "memory");
|
||||
return value;
|
||||
}
|
||||
|
||||
// The generic timer runs from boot at CNTFRQ_EL0 Hz, so it times short
|
||||
// delays without calibration.
|
||||
inline void IoDelay() {
|
||||
uint64_t frequency;
|
||||
asm volatile("mrs %0, cntfrq_el0" : "=r"(frequency));
|
||||
uint64_t ticks = frequency / 1000000 + 1;
|
||||
uint64_t start = ReadTimestamp();
|
||||
while (ReadTimestamp() - start < ticks) {
|
||||
Pause();
|
||||
}
|
||||
}
|
||||
|
||||
// ARMv8 has no "clean the whole cache" instruction: set/way maintenance
|
||||
// is only safe from firmware, which PSCI performs on power transitions.
|
||||
// Order outstanding accesses so the firmware call sees them.
|
||||
inline void WritebackInvalidateCaches() {
|
||||
asm volatile("dsb sy\n\tisb" ::: "memory");
|
||||
}
|
||||
|
||||
inline void FlushTlbPage(uint64_t virtualAddress) {
|
||||
asm volatile("dsb ishst\n\ttlbi vaae1is, %0\n\tdsb ish\n\tisb"
|
||||
:: "r"(virtualAddress >> 12) : "memory");
|
||||
}
|
||||
|
||||
inline void FlushTlbAll() {
|
||||
asm volatile("dsb ishst\n\ttlbi vmalle1is\n\tdsb ish\n\tisb" ::: "memory");
|
||||
}
|
||||
|
||||
// The process (lower) half of the address space is translated through
|
||||
// TTBR0_EL1; the kernel half through TTBR1_EL1, which never changes.
|
||||
static constexpr uint64_t TtbrAddressMask = 0x0000FFFFFFFFFFFEULL;
|
||||
|
||||
inline uint64_t ReadPageTableRoot() {
|
||||
uint64_t ttbr;
|
||||
asm volatile("mrs %0, ttbr0_el1" : "=r"(ttbr));
|
||||
return ttbr & TtbrAddressMask;
|
||||
}
|
||||
|
||||
// No ASIDs yet: every switch invalidates the old process's entries.
|
||||
inline void WritePageTableRoot(uint64_t rootPhys) {
|
||||
asm volatile("dsb ishst\n\tmsr ttbr0_el1, %0\n\tisb\n\ttlbi vmalle1\n\tdsb nsh\n\tisb"
|
||||
:: "r"(rootPhys) : "memory");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* CpuPower.cpp
|
||||
* aarch64 CPU power management (implements Arch/CpuPower.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/CpuPower.hpp>
|
||||
|
||||
// No frequency scaling or thermal governor yet (on ARM these come from
|
||||
// ACPI CPPC / SCMI rather than CPU registers).
|
||||
|
||||
namespace Arch::CpuPower {
|
||||
|
||||
void InitializeBsp() {}
|
||||
void InitializeAp() {}
|
||||
void ApplyPolicyIfChanged() {}
|
||||
void ThermalTick(uint64_t) {}
|
||||
void ServiceDeferredDiagnostics() {}
|
||||
void ReapplyAfterWake() {}
|
||||
|
||||
bool GetSnapshot(Snapshot& out) {
|
||||
out = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* InterruptFrame.hpp
|
||||
* aarch64 exception frame
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// What the exception vectors save before calling into C++.
|
||||
struct PanicFrame {
|
||||
uint64_t x[31]; // x0-x30
|
||||
uint64_t sp; // interrupted SP (SP_EL0 for EL0, else SP_EL1)
|
||||
uint64_t elr; // ELR_EL1: faulting/preferred-return PC
|
||||
uint64_t spsr; // SPSR_EL1
|
||||
uint64_t esr; // ESR_EL1: exception syndrome
|
||||
uint64_t far; // FAR_EL1: faulting address
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Interrupts.cpp
|
||||
* aarch64 IRQ slot dispatch (implements Arch/Interrupts.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Interrupts.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
// IRQ slots are delivered by the interrupt controller driver (GICv2/v3,
|
||||
// not written yet), which maps INTIDs onto slots and calls ArchIrqDispatch
|
||||
// from the IRQ exception vector after acknowledging the interrupt.
|
||||
|
||||
namespace Arch {
|
||||
|
||||
static IrqHandler g_irqHandlers[IRQ_COUNT] = {};
|
||||
|
||||
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
|
||||
if (irq >= IRQ_COUNT) return;
|
||||
g_irqHandlers[irq] = handler;
|
||||
Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ "
|
||||
<< base::dec << (uint64_t)irq;
|
||||
}
|
||||
|
||||
// TODO(aarch64): GIC SPI routing (ACPI MADT GICD / interrupt overrides).
|
||||
void RouteLegacyIrq(uint8_t irq) {
|
||||
Kt::KernelLogStream(Kt::WARNING, "IRQ") << "aarch64: cannot route IRQ "
|
||||
<< base::dec << (uint64_t)irq << " (no GIC driver)";
|
||||
}
|
||||
|
||||
void EnableLegacyIrq(uint8_t irq) {
|
||||
RouteLegacyIrq(irq);
|
||||
}
|
||||
|
||||
// TODO(aarch64): GICv3 ITS / GICv2m doorbell.
|
||||
bool ComposeMsi(uint8_t, MsiMessage&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(aarch64): GIC software-generated interrupts.
|
||||
bool SendIpi(int, uint8_t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" void ArchIrqDispatch(uint64_t irq, bool fromUser) {
|
||||
if (irq < Arch::IRQ_COUNT && Arch::g_irqHandlers[irq] != nullptr) {
|
||||
Arch::g_irqHandlers[irq]((uint8_t)irq, fromUser);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* IoPort.hpp
|
||||
* aarch64 port I/O (implements Io/IoPort.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Cpu.hpp>
|
||||
|
||||
// ARM has no I/O address space. Until a PCI host bridge driver maps its I/O
|
||||
// aperture, port accesses behave like accesses to an absent device.
|
||||
|
||||
namespace Io {
|
||||
static constexpr bool HasPortIo = false;
|
||||
|
||||
inline void Out8(uint8_t, uint16_t) {}
|
||||
inline void Out16(uint16_t, uint16_t) {}
|
||||
inline void Out32(uint32_t, uint16_t) {}
|
||||
|
||||
inline uint8_t In8(uint16_t) { return 0xFF; }
|
||||
inline uint16_t In16(uint16_t) { return 0xFFFF; }
|
||||
inline uint32_t In32(uint16_t) { return 0xFFFFFFFF; }
|
||||
|
||||
inline void IoPortWait() {
|
||||
Arch::IoDelay();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Paging.cpp
|
||||
* aarch64 virtual memory (implements Memory/Paging.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Common/Panic.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
|
||||
// TODO(aarch64): 4 KiB-granule, 48-bit VA translation tables. The kernel
|
||||
// half lives in TTBR1_EL1 and is never switched; "address space roots"
|
||||
// handed to the generic kernel are TTBR0_EL1 tables, so
|
||||
// CreateUserAddressSpace has no kernel half to copy. MAIR_EL1 needs
|
||||
// Normal-WB, Normal-NC (MapWC) and Device-nGnRE (MapMMIO) indices.
|
||||
|
||||
namespace Memory::VMM {
|
||||
|
||||
Paging* g_paging = nullptr;
|
||||
|
||||
static kcp::Mutex pagingLock;
|
||||
|
||||
void LockUserPaging() { pagingLock.Acquire(); }
|
||||
void UnlockUserPaging() { pagingLock.Release(); }
|
||||
|
||||
[[noreturn]] static void NotImplemented(const char* what) {
|
||||
Panic(what, nullptr);
|
||||
}
|
||||
|
||||
void ActivateKernelAddressSpace() {
|
||||
Arch::WritePageTableRoot(g_paging->KernelRoot());
|
||||
}
|
||||
|
||||
void FlushTLB() {
|
||||
Arch::FlushTlbAll();
|
||||
}
|
||||
|
||||
Paging::Paging() {
|
||||
NotImplemented("aarch64: virtual memory not implemented");
|
||||
}
|
||||
|
||||
void Paging::Init(std::uint64_t, std::uint64_t, const montauk::boot::MemoryMap&,
|
||||
const montauk::boot::Framebuffer&) {
|
||||
NotImplemented("aarch64: Paging::Init not implemented");
|
||||
}
|
||||
|
||||
std::uint64_t Paging::HhdmLimit() { return 0; }
|
||||
|
||||
void Paging::Map(std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::Map not implemented");
|
||||
}
|
||||
|
||||
void Paging::MapMMIO(std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::MapMMIO not implemented");
|
||||
}
|
||||
|
||||
void Paging::MapWC(std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::MapWC not implemented");
|
||||
}
|
||||
|
||||
void Paging::MapUser(std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::MapUser not implemented");
|
||||
}
|
||||
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::GetPhysAddr not implemented");
|
||||
}
|
||||
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t virtualAddress) {
|
||||
return GetPhysAddr(rootPhys, virtualAddress);
|
||||
}
|
||||
|
||||
std::uint64_t Paging::CreateUserAddressSpace() {
|
||||
NotImplemented("aarch64: Paging::CreateUserAddressSpace not implemented");
|
||||
}
|
||||
|
||||
bool Paging::MapUserIn(std::uint64_t, std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::MapUserIn not implemented");
|
||||
}
|
||||
|
||||
bool Paging::MapUserInPermissions(std::uint64_t, std::uint64_t, std::uint64_t, bool, bool) {
|
||||
NotImplemented("aarch64: Paging::MapUserInPermissions not implemented");
|
||||
}
|
||||
|
||||
bool Paging::ProtectUserIn(std::uint64_t, std::uint64_t, bool, bool) {
|
||||
NotImplemented("aarch64: Paging::ProtectUserIn not implemented");
|
||||
}
|
||||
|
||||
bool Paging::MapUserInWC(std::uint64_t, std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::MapUserInWC not implemented");
|
||||
}
|
||||
|
||||
void Paging::UnmapUserIn(std::uint64_t, std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::UnmapUserIn not implemented");
|
||||
}
|
||||
|
||||
void Paging::FreeUserHalf(std::uint64_t) {
|
||||
NotImplemented("aarch64: Paging::FreeUserHalf not implemented");
|
||||
}
|
||||
|
||||
bool Paging::IsUserRangeAccessible(std::uint64_t, std::uint64_t, std::uint64_t, bool) {
|
||||
NotImplemented("aarch64: Paging::IsUserRangeAccessible not implemented");
|
||||
}
|
||||
|
||||
void Paging::MapEfiRuntime(const montauk::boot::EfiInfo&) {
|
||||
NotImplemented("aarch64: Paging::MapEfiRuntime not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* PanicFrame.cpp
|
||||
* aarch64 CPU state for the panic screen
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Common/Panic.hpp>
|
||||
#include <Arch/aarch64/InterruptFrame.hpp>
|
||||
#include <CppLib/BoxUI.hpp>
|
||||
|
||||
static constexpr int BoxWidth = PanicBoxWidth;
|
||||
|
||||
namespace Arch {
|
||||
void PrintPanicFrame(PanicFrame* frame) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "CPU State:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Exception Class (ESR)", frame->esr >> 26, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Syndrome (ESR)", frame->esr, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Fault Address (FAR)", frame->far, BoxWidth);
|
||||
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Program Counter (ELR)", frame->elr, BoxWidth);
|
||||
PrintBoxedHex(kerr, "PSTATE (SPSR)", frame->spsr, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Pointer", frame->sp, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Link Register (x30)", frame->x[30], BoxWidth);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* PerCpu.hpp
|
||||
* aarch64 per-CPU state (see Arch/Smp.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Read by the exception vectors through TPIDR_EL1.
|
||||
struct CpuEntryState {
|
||||
uint64_t kernelStackTop; // SP_EL1 to use on entry from EL0
|
||||
uint64_t scratch; // vector-private scratch slot
|
||||
};
|
||||
|
||||
struct CpuState {
|
||||
uint64_t mpidr; // MPIDR_EL1 affinity of this CPU
|
||||
};
|
||||
|
||||
// TPIDR_EL1 holds the CpuData pointer (zeroed in EarlyInitialize until
|
||||
// Smp::InitBsp installs it).
|
||||
inline void* CurrentCpuLocal() {
|
||||
void* ptr;
|
||||
asm volatile("mrs %0, tpidr_el1" : "=r"(ptr));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline void* TryCurrentCpuLocal() {
|
||||
return CurrentCpuLocal();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Platform.cpp
|
||||
* aarch64 bring-up hooks (implements Arch/Platform.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Platform.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Arch {
|
||||
|
||||
void EarlyInitialize() {
|
||||
// No per-CPU data yet: Smp::TryGetCurrentCpuData must see null.
|
||||
asm volatile("msr tpidr_el1, xzr");
|
||||
|
||||
// CPACR_EL1.FPEN = 0b11: FP/SIMD usable at EL0 and EL1 (the kernel
|
||||
// itself is built -mgeneral-regs-only; Context.S saves user state).
|
||||
uint64_t cpacr;
|
||||
asm volatile("mrs %0, cpacr_el1" : "=r"(cpacr));
|
||||
cpacr |= 3ULL << 20;
|
||||
asm volatile("msr cpacr_el1, %0\n\tisb" :: "r"(cpacr));
|
||||
}
|
||||
|
||||
// TODO(aarch64): install the VBAR_EL1 vector table (EL1/EL0 synchronous
|
||||
// exceptions -> panic / SyscallDispatch, IRQ -> GIC -> ArchIrqDispatch).
|
||||
void InitializeTraps() {
|
||||
KernelLogStream(WARNING, "Arch") << "aarch64: exception vectors not implemented";
|
||||
}
|
||||
|
||||
// MAIR_EL1 attribute indices are chosen together with the page-table
|
||||
// format (Paging.cpp), so there is nothing to program separately.
|
||||
void InitializeMemoryAttributes() {
|
||||
}
|
||||
|
||||
// TODO(aarch64): GIC distributor/redistributor from the ACPI MADT.
|
||||
void InitializeInterrupts(Hal::ACPI::CommonSDTHeader*) {
|
||||
Smp::InitBsp();
|
||||
KernelLogStream(WARNING, "Arch") << "aarch64: no interrupt controller driver";
|
||||
}
|
||||
|
||||
void InitializePlatformDevices() {
|
||||
}
|
||||
|
||||
// EL0 entry needs no global state beyond Arch::ActivateThread.
|
||||
void PrepareUserMode() {
|
||||
}
|
||||
|
||||
void LateInitialize() {
|
||||
}
|
||||
|
||||
void ListPlatformDevices(PlatformDeviceSink, void*) {
|
||||
}
|
||||
|
||||
// TODO(aarch64): PSCI SYSTEM_RESET through the conduit the FADT names.
|
||||
void Reboot() {
|
||||
HaltForever();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Smp.cpp
|
||||
* aarch64 per-CPU data (implements the arch half of Arch/Smp.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Smp {
|
||||
|
||||
static CpuData g_cpus[MaxCPUs];
|
||||
static int g_cpuCount = 0;
|
||||
static int g_detectedCpuCount = 1;
|
||||
|
||||
CpuData* GetCpuData(int index) {
|
||||
if (index < 0 || index >= g_cpuCount) return nullptr;
|
||||
return &g_cpus[index];
|
||||
}
|
||||
|
||||
int GetCpuCount() {
|
||||
return g_cpuCount;
|
||||
}
|
||||
|
||||
int GetDetectedCpuCount() {
|
||||
return g_detectedCpuCount;
|
||||
}
|
||||
|
||||
void InitBsp() {
|
||||
memset(g_cpus, 0, sizeof(g_cpus));
|
||||
|
||||
CpuData& bsp = g_cpus[0];
|
||||
bsp.selfPtr = (uint64_t)&bsp;
|
||||
bsp.cpuIndex = 0;
|
||||
bsp.currentSlot = -1;
|
||||
bsp.started = true;
|
||||
asm volatile("mrs %0, mpidr_el1" : "=r"(bsp.arch.mpidr));
|
||||
|
||||
asm volatile("msr tpidr_el1, %0" :: "r"(&bsp));
|
||||
g_cpuCount = 1;
|
||||
|
||||
KernelLogStream(OK, "SMP") << "BSP initialized (MPIDR " << base::hex
|
||||
<< bsp.arch.mpidr << ")";
|
||||
}
|
||||
|
||||
void BootAPs(const montauk::boot::SmpInfo& smp) {
|
||||
if (smp.cpuCount > 1) {
|
||||
g_detectedCpuCount = (int)smp.cpuCount;
|
||||
}
|
||||
|
||||
// TODO(aarch64): start secondaries through smp.startCpu once the GIC
|
||||
// (reschedule/TLB-shootdown SGIs) and the per-CPU generic timer work.
|
||||
KernelLogStream(WARNING, "SMP") << "aarch64: secondary CPUs not started ("
|
||||
<< base::dec << smp.cpuCount << " reported)";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Syscall.cpp
|
||||
* aarch64 system-call entry (implements Arch/Syscall.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Syscall.hpp>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// `svc #0` arrives through the EL0 synchronous vector installed by
|
||||
// InitializeTraps; there is no per-CPU register to program.
|
||||
void InitializeSyscallEntry() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* SyscallFrame.hpp
|
||||
* aarch64 system-call frame (see Arch/Syscall.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Saved by the EL0 synchronous-exception vector for `svc #0`:
|
||||
// x0-x5 carry the arguments and x8 the system-call number.
|
||||
struct SyscallFrame {
|
||||
uint64_t arg1, arg2, arg3, arg4, arg5, arg6; // x0-x5
|
||||
uint64_t syscall_nr; // x8
|
||||
uint64_t elr, spsr, sp_el0; // user PC, PSTATE, SP
|
||||
uint64_t x19_x30[12]; // callee-saved + LR
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Timer.cpp
|
||||
* aarch64 tick timer (implements Arch/Timer.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Timer.hpp>
|
||||
|
||||
// TODO(aarch64): drive the EL1 virtual timer (CNTV_TVAL/CNTV_CTL) from
|
||||
// CNTFRQ_EL0, re-arming on each tick, once the GIC delivers its PPI.
|
||||
|
||||
namespace Arch::Timer {
|
||||
|
||||
bool InitializeBsp(uint32_t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void StartPeriodic(uint32_t) {
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Abi.hpp
|
||||
* x86_64 user-mode ABI details (implements Arch/Abi.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// UEFI uses the Microsoft x64 calling convention.
|
||||
#define ARCH_EFIAPI __attribute__((__ms_abi__))
|
||||
|
||||
namespace Arch {
|
||||
|
||||
constexpr uint16_t ElfMachine = 62; // EM_X86_64
|
||||
constexpr const char* ElfMachineName = "x86_64";
|
||||
|
||||
inline RelocationKind ClassifyRelocation(uint32_t type) {
|
||||
switch (type) {
|
||||
case 0: return RelocationKind::None;
|
||||
case 8: return RelocationKind::Relative; // R_X86_64_RELATIVE
|
||||
case 1: // R_X86_64_64
|
||||
case 6: // R_X86_64_GLOB_DAT
|
||||
case 7: return RelocationKind::Symbol; // R_X86_64_JUMP_SLOT
|
||||
default: return RelocationKind::Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
// Variant II. The linker addresses TLS variables at
|
||||
// TP - align_up(memSize, p_align) + offset, so the template must start
|
||||
// exactly that far below TP; rounding p_align up first (as an older
|
||||
// version did) shifts every variable away from its initial value.
|
||||
// TP itself is kept at least 16-byte aligned, which the gap between
|
||||
// base and the template absorbs. The 16 bytes above TP are the TCB.
|
||||
inline TlsLayout ComputeTlsLayout(uint64_t memSize, uint64_t pAlign) {
|
||||
uint64_t align = pAlign ? pAlign : 1;
|
||||
uint64_t block = (memSize + align - 1) & ~(align - 1);
|
||||
uint64_t tpAlign = align < 16 ? 16 : align;
|
||||
uint64_t tp = (block + tpAlign - 1) & ~(tpAlign - 1);
|
||||
return {tp - block, tp, tp + 16};
|
||||
}
|
||||
|
||||
inline void WriteExitStub(uint8_t* stub) {
|
||||
stub[0] = 0x31; stub[1] = 0xFF; // xor edi, edi (exit code 0)
|
||||
stub[2] = 0x31; stub[3] = 0xC0; // xor eax, eax (SYS_EXIT = 0)
|
||||
stub[4] = 0x0F; stub[5] = 0x05; // syscall
|
||||
}
|
||||
|
||||
// The entry function is entered as if CALLed: the return address sits
|
||||
// at [RSP], with RSP = 16n - 8.
|
||||
inline uint64_t SetupUserEntryStack(uint8_t* topPage, uint64_t stackTop,
|
||||
uint64_t returnAddress) {
|
||||
*(uint64_t*)(topPage + 0xFF8) = returnAddress;
|
||||
return stackTop - 8;
|
||||
}
|
||||
|
||||
// SystemV alignment as if CALLed (16n - 8); the slot is never read.
|
||||
inline uint64_t ThreadEntryStackPointer(uint64_t stackTop) {
|
||||
return (stackTop & ~0xFULL) - 8;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,8 +4,10 @@
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "AcpiSleep.hpp"
|
||||
#include "AcpiEvents.hpp"
|
||||
#include <ACPI/AcpiSleep.hpp>
|
||||
#include <ACPI/AcpiEvents.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/x86_64/Acpi/S3State.hpp>
|
||||
#include <ACPI/FADT.hpp>
|
||||
#include <ACPI/AML/AmlParser.hpp>
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
@@ -14,19 +16,19 @@
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/IoApic.hpp>
|
||||
#include <Hal/Apic/Pic.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Arch/x86_64/GDT.hpp>
|
||||
#include <Arch/x86_64/Apic/Apic.hpp>
|
||||
#include <Arch/x86_64/Apic/IoApic.hpp>
|
||||
#include <Arch/x86_64/Apic/Pic.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Arch/CpuPower.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
#include <Drivers/Graphics/DgpuPower.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Graphics/Framebuffer.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Arch/x86_64/MSR.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
@@ -186,7 +188,7 @@ namespace Hal {
|
||||
<< base::hex << (uint64_t)g_s3SlpTypA
|
||||
<< " SLP_TYPb=" << base::hex << (uint64_t)g_s3SlpTypB << ")";
|
||||
KernelLogStream(INFO, "S3") << "Kernel PML4 phys = " << base::hex
|
||||
<< (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
<< Memory::VMM::g_paging->KernelRoot();
|
||||
KernelLogStream(INFO, "S3") << "FACS at phys " << base::hex
|
||||
<< (uint64_t)fadt.FacsAddress << " version=" << base::dec
|
||||
<< (uint64_t)g_facs->Version << " flags=" << base::hex
|
||||
@@ -314,7 +316,7 @@ namespace Hal {
|
||||
// shadow copy of the PML4 at a fixed low-memory address.
|
||||
// Only the PML4 page itself needs to be below 4GB — its entries
|
||||
// contain full 52-bit physical addresses for sub-tables.
|
||||
uint64_t kernelPml4Phys = (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
uint64_t kernelPml4Phys = Memory::VMM::g_paging->KernelRoot();
|
||||
uint8_t* kernelPml4Virt = (uint8_t*)Memory::HHDM(kernelPml4Phys);
|
||||
uint8_t* shadowPml4Virt = (uint8_t*)Memory::HHDM((uint64_t)SHADOW_PML4_PHYS);
|
||||
memcpy(shadowPml4Virt, kernelPml4Virt, 4096);
|
||||
@@ -439,7 +441,7 @@ namespace Hal {
|
||||
Hal::InitializePAT();
|
||||
// HWP enable and POWER_CTL reset across S3; re-apply them and
|
||||
// bump the policy epoch so APs re-apply on their next tick.
|
||||
Hal::CpuPower::ReapplyAfterWake();
|
||||
Arch::CpuPower::ReapplyAfterWake();
|
||||
reinitProgress(0x05); // PIC
|
||||
Hal::DisableLegacyPic();
|
||||
reinitProgress(0x06); // Local APIC
|
||||
@@ -447,7 +449,7 @@ namespace Hal {
|
||||
reinitProgress(0x07); // IO APIC
|
||||
Hal::IoApic::Reinitialize();
|
||||
reinitProgress(0x08); // APIC Timer
|
||||
Timekeeping::ApicTimerReinitialize();
|
||||
Timekeeping::TimerReinitialize();
|
||||
reinitProgress(0x09); // PM1 clear
|
||||
ClearPM1Status();
|
||||
reinitProgress(0x0A); // _WAK
|
||||
@@ -463,7 +465,7 @@ namespace Hal {
|
||||
reinitProgress(0x0D); // discrete GPU back off
|
||||
Drivers::Graphics::DgpuPower::Resume();
|
||||
reinitProgress(0x0E); // sti
|
||||
asm volatile("sti");
|
||||
Arch::EnableInterrupts();
|
||||
|
||||
KernelLogStream(OK, "S3") << "Resumed from S3 suspend";
|
||||
|
||||
@@ -517,7 +519,7 @@ namespace Hal {
|
||||
Io::Out8(0x00, 0x71);
|
||||
|
||||
// 3. Disable interrupts
|
||||
asm volatile("cli");
|
||||
Arch::DisableInterrupts();
|
||||
|
||||
// 4. Identity-map the trampoline page so the CPU can execute it
|
||||
// after paging is re-enabled with our CR3 during wake.
|
||||
@@ -525,7 +527,7 @@ namespace Hal {
|
||||
Memory::VMM::g_paging->Map(TRAMPOLINE_PHYS, TRAMPOLINE_PHYS);
|
||||
|
||||
// 5. Flush all caches
|
||||
asm volatile("wbinvd");
|
||||
Arch::WritebackInvalidateCaches();
|
||||
|
||||
// 6. Set the waking vector in FACS
|
||||
SetWakingVector();
|
||||
@@ -548,7 +550,7 @@ namespace Hal {
|
||||
}
|
||||
|
||||
// 10. Flush caches again
|
||||
asm volatile("wbinvd");
|
||||
Arch::WritebackInvalidateCaches();
|
||||
|
||||
// 11. Assert SLP_EN — this triggers S3 entry
|
||||
Io::Out16(pm1a | PM1_SLP_EN, (uint16_t)g_pm1aControlBlock);
|
||||
@@ -563,7 +565,7 @@ namespace Hal {
|
||||
// on QEMU/KVM the vCPU must reach HLT before QEMU finalizes S3.
|
||||
// On resume, firmware jumps to our waking vector.
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
Arch::Halt();
|
||||
// If we wake from HLT (spurious or real wake), check WAK_STS
|
||||
if (g_pm1aEventBlock != 0) {
|
||||
uint16_t sts = Io::In16((uint16_t)g_pm1aEventBlock);
|
||||
@@ -574,7 +576,7 @@ namespace Hal {
|
||||
// If we get here, either S3 failed or we woke immediately.
|
||||
// The normal wake path goes through AcpiResumeLongMode instead.
|
||||
// Re-enable interrupts as a safety measure.
|
||||
asm volatile("sti");
|
||||
Arch::EnableInterrupts();
|
||||
|
||||
KernelLogStream(WARNING, "S3") << "S3 entry may have failed (fell through)";
|
||||
return 0;
|
||||
@@ -5,10 +5,11 @@
|
||||
*/
|
||||
|
||||
#include "CpuIdle.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <ACPI/FADT.hpp>
|
||||
#include <ACPI/AML/AmlInterpreter.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Arch/x86_64/CpuFeatures.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -480,7 +481,7 @@ namespace Hal {
|
||||
if (hasMwait) {
|
||||
Hal::IdleWaitWithInterruptsDisabled(addr, CurrentIdleHint());
|
||||
} else {
|
||||
Hal::HaltWithInterruptsDisabled();
|
||||
Arch::HaltWithInterruptsDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* S3State.hpp
|
||||
* CPU state saved across S3 by S3Wake.asm
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Hal {
|
||||
namespace AcpiSleep {
|
||||
|
||||
// ============================================================================
|
||||
// Layout must match S3Wake.asm offsets exactly.
|
||||
// No FPU/SSE state — the kernel is compiled with -mno-sse.
|
||||
struct CpuState {
|
||||
uint64_t Rax, Rbx, Rcx, Rdx; // 0x00 - 0x18
|
||||
uint64_t Rsi, Rdi, Rbp, Rsp; // 0x20 - 0x38
|
||||
uint64_t R8, R9, R10, R11; // 0x40 - 0x58
|
||||
uint64_t R12, R13, R14, R15; // 0x60 - 0x78
|
||||
uint64_t Rflags; // 0x80
|
||||
uint64_t Rip; // 0x88
|
||||
uint64_t Cr3; // 0x90
|
||||
uint64_t Cr0, Cr4; // 0x98, 0xA0
|
||||
uint8_t GdtPtr[10]; // 0xA8
|
||||
uint8_t IdtPtr[10]; // 0xB2
|
||||
} __attribute__((aligned(16)));
|
||||
|
||||
};
|
||||
};
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -10,7 +10,7 @@
|
||||
#include "IoApic.hpp"
|
||||
#include "Interrupts.hpp"
|
||||
#include <ACPI/MADT.hpp>
|
||||
#include <Hal/IDT.hpp>
|
||||
#include <Arch/x86_64/IDT.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
@@ -67,8 +67,8 @@ namespace Hal {
|
||||
// Step 7: Route keyboard (IRQ1) and mouse (IRQ12) to BSP
|
||||
uint8_t bspApicId = (uint8_t)LocalApic::GetId();
|
||||
|
||||
IoApic::RouteIrq(IRQ_KEYBOARD, IRQ_VECTOR_BASE + IRQ_KEYBOARD, bspApicId);
|
||||
IoApic::RouteIrq(IRQ_MOUSE, IRQ_VECTOR_BASE + IRQ_MOUSE, bspApicId);
|
||||
IoApic::RouteIrq(Arch::IRQ_KEYBOARD, IRQ_VECTOR_BASE + Arch::IRQ_KEYBOARD, bspApicId);
|
||||
IoApic::RouteIrq(Arch::IRQ_MOUSE, IRQ_VECTOR_BASE + Arch::IRQ_MOUSE, bspApicId);
|
||||
|
||||
// Note: sti is NOT called here. The caller (Main.cpp) enables
|
||||
// interrupts after setting up per-CPU GS base for SMP/SWAPGS.
|
||||
File renamed without changes.
@@ -6,7 +6,9 @@
|
||||
|
||||
#include "Interrupts.hpp"
|
||||
#include "Apic.hpp"
|
||||
#include <Hal/IDT.hpp>
|
||||
#include "IoApic.hpp"
|
||||
#include <Arch/x86_64/IDT.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
@@ -14,19 +16,15 @@
|
||||
extern "C" void* IrqStubTable[Hal::IRQ_COUNT];
|
||||
extern "C" void IrqStubSpurious();
|
||||
|
||||
// Local APIC message address range; destination APIC ID in bits 19:12.
|
||||
static constexpr uint64_t MsiAddressBase = 0xFEE00000;
|
||||
|
||||
namespace Hal {
|
||||
// Dispatch table: one handler per IRQ
|
||||
static IrqHandler g_irqHandlers[IRQ_COUNT] = {};
|
||||
|
||||
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
|
||||
if (irq >= IRQ_COUNT) return;
|
||||
g_irqHandlers[irq] = handler;
|
||||
Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ " << base::dec << (uint64_t)irq
|
||||
<< " (vector " << (uint64_t)(IRQ_VECTOR_BASE + irq) << ")";
|
||||
}
|
||||
static Arch::IrqHandler g_irqHandlers[IRQ_COUNT] = {};
|
||||
|
||||
void InitializeIrqHandlers() {
|
||||
// Install IRQ stubs into IDT vectors 32..55
|
||||
// Install IRQ stubs into IDT vectors 32..79
|
||||
for (int i = 0; i < IRQ_COUNT; i++) {
|
||||
IDTEncodeInterrupt(IRQ_VECTOR_BASE + i, IrqStubTable[i], 0x8E);
|
||||
}
|
||||
@@ -41,6 +39,41 @@ namespace Hal {
|
||||
|
||||
};
|
||||
|
||||
namespace Arch {
|
||||
void RegisterIrqHandler(uint8_t irq, IrqHandler handler) {
|
||||
if (irq >= IRQ_COUNT) return;
|
||||
Hal::g_irqHandlers[irq] = handler;
|
||||
Kt::KernelLogStream(Kt::DEBUG, "IRQ") << "Registered handler for IRQ " << base::dec << (uint64_t)irq
|
||||
<< " (vector " << (uint64_t)(Hal::IRQ_VECTOR_BASE + irq) << ")";
|
||||
}
|
||||
|
||||
void RouteLegacyIrq(uint8_t irq) {
|
||||
// RouteIrq applies the MADT's Interrupt Source Overrides (polarity,
|
||||
// trigger mode, GSI remapping).
|
||||
Hal::IoApic::RouteIrq(irq, Hal::IRQ_VECTOR_BASE + irq,
|
||||
(uint8_t)Hal::LocalApic::GetId());
|
||||
}
|
||||
|
||||
void EnableLegacyIrq(uint8_t irq) {
|
||||
Hal::IoApic::UnmaskIrq(Hal::IoApic::GetGsiForIrq(irq));
|
||||
}
|
||||
|
||||
bool ComposeMsi(uint8_t irq, MsiMessage& out) {
|
||||
// Physical destination mode, fixed delivery, edge triggered, to the
|
||||
// BSP (APIC ID 0 -- the destination field is left zero, as every
|
||||
// driver has always programmed it).
|
||||
out = MsiMessage{ MsiAddressBase, (uint32_t)(Hal::IRQ_VECTOR_BASE + irq) };
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SendIpi(int cpuIndex, uint8_t irq) {
|
||||
auto* cpu = Smp::GetCpuData(cpuIndex);
|
||||
if (cpu == nullptr) return false;
|
||||
return Hal::LocalApic::SendFixedIpi(cpu->arch.lapicId,
|
||||
Hal::IRQ_VECTOR_BASE + irq);
|
||||
}
|
||||
};
|
||||
|
||||
// C linkage dispatch function called from assembly stubs
|
||||
extern "C" void HalIrqDispatch(uint64_t irqNumber, uint64_t savedCs) {
|
||||
// Send EOI BEFORE calling the handler. The handler may context-switch
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Interrupts.hpp
|
||||
* x86_64 IRQ slot <-> IDT vector mapping
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/Interrupts.hpp>
|
||||
|
||||
namespace Hal {
|
||||
using Arch::IRQ_COUNT;
|
||||
|
||||
// IRQ vector base: IRQ slot N is delivered on IDT vector 32 + N.
|
||||
constexpr uint8_t IRQ_VECTOR_BASE = 32;
|
||||
|
||||
// Install IRQ stubs into the IDT and set up the dispatch table
|
||||
void InitializeIrqHandlers();
|
||||
|
||||
};
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* ApicTimer.cpp
|
||||
* Local APIC timer, PIT-calibrated (implements Arch/Timer.hpp)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Timer.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/x86_64/Apic/Apic.hpp>
|
||||
#include <Arch/x86_64/Apic/Interrupts.hpp>
|
||||
#include <Io/IoPort.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Arch::Timer {
|
||||
// PIT constants
|
||||
static constexpr uint32_t PIT_FREQUENCY = 1193182; // PIT oscillator frequency in Hz
|
||||
static constexpr uint16_t PIT_CHANNEL2_DATA = 0x42;
|
||||
static constexpr uint16_t PIT_COMMAND = 0x43;
|
||||
static constexpr uint16_t PIT_GATE_PORT = 0x61;
|
||||
|
||||
// APIC timer LVT mode bits
|
||||
static constexpr uint32_t LVT_MASKED = (1 << 16);
|
||||
static constexpr uint32_t LVT_PERIODIC = (1 << 17);
|
||||
|
||||
// APIC timer divide configuration values
|
||||
static constexpr uint32_t DIVIDE_BY_16 = 0x03;
|
||||
|
||||
static uint32_t g_ticksPerMs = 0;
|
||||
|
||||
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
|
||||
return g_ticksPerMs * intervalMs;
|
||||
}
|
||||
|
||||
static void ProgramTimer(bool periodic, uint32_t intervalMs) {
|
||||
uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Arch::IRQ_TIMER);
|
||||
if (periodic) {
|
||||
lvt |= LVT_PERIODIC;
|
||||
}
|
||||
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL,
|
||||
CountForIntervalMs(intervalMs));
|
||||
}
|
||||
|
||||
// Use PIT channel 2 to create a precise delay for calibration.
|
||||
// Returns the number of APIC timer ticks that elapsed during ~10ms.
|
||||
static uint32_t CalibratePit() {
|
||||
// We want to measure ~10ms worth of APIC timer ticks.
|
||||
// PIT count for 10ms: PIT_FREQUENCY / 100 = 11931
|
||||
uint16_t pitCount = PIT_FREQUENCY / 100;
|
||||
|
||||
// Set up PIT channel 2 in one-shot mode (mode 0), lobyte/hibyte
|
||||
// Command: channel 2 (bits 7-6 = 10), lobyte/hibyte (bits 5-4 = 11),
|
||||
// mode 0 (bits 3-1 = 000), binary (bit 0 = 0) => 0xB0
|
||||
Io::Out8(0xB0, PIT_COMMAND);
|
||||
|
||||
// Disable PIT channel 2 gate to prepare
|
||||
uint8_t gate = Io::In8(PIT_GATE_PORT);
|
||||
gate &= ~0x01; // Disable gate (bit 0 = 0)
|
||||
gate &= ~0x02; // Disable speaker (bit 1 = 0)
|
||||
Io::Out8(gate, PIT_GATE_PORT);
|
||||
|
||||
// Load the count value (low byte first, then high byte)
|
||||
Io::Out8((uint8_t)(pitCount & 0xFF), PIT_CHANNEL2_DATA);
|
||||
Io::IoPortWait();
|
||||
Io::Out8((uint8_t)(pitCount >> 8), PIT_CHANNEL2_DATA);
|
||||
|
||||
// Set APIC timer to max count with divide-by-16, one-shot (masked so no IRQ)
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, LVT_MASKED);
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0xFFFFFFFF);
|
||||
|
||||
// Enable PIT channel 2 gate to start counting
|
||||
gate = Io::In8(PIT_GATE_PORT);
|
||||
gate |= 0x01; // Enable gate (bit 0 = 1)
|
||||
Io::Out8(gate, PIT_GATE_PORT);
|
||||
|
||||
// Wait for PIT channel 2 output to go high (bit 5 of port 0x61)
|
||||
while (!(Io::In8(PIT_GATE_PORT) & 0x20)) {
|
||||
Arch::Pause();
|
||||
}
|
||||
|
||||
// Read how many APIC timer ticks elapsed
|
||||
uint32_t currentCount = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_CURRENT);
|
||||
uint32_t elapsed = 0xFFFFFFFF - currentCount;
|
||||
|
||||
// Stop the APIC timer
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0);
|
||||
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
bool InitializeBsp(uint32_t periodMs) {
|
||||
KernelLogStream(INFO, "Timer") << "Calibrating APIC timer using PIT channel 2";
|
||||
|
||||
// Calibrate: measure APIC ticks over ~10ms
|
||||
uint32_t ticksIn10ms = CalibratePit();
|
||||
g_ticksPerMs = ticksIn10ms / 10;
|
||||
|
||||
if (g_ticksPerMs == 0) {
|
||||
KernelLogStream(ERROR, "Timer") << "APIC timer calibration failed (0 ticks/ms)";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t timerFreqHz = (uint64_t)g_ticksPerMs * 1000;
|
||||
|
||||
KernelLogStream(OK, "Timer") << "APIC timer: " << base::dec << (uint64_t)g_ticksPerMs
|
||||
<< " ticks/ms (" << timerFreqHz << " Hz, divide-by-16)";
|
||||
|
||||
// Periodic mode on vector IRQ_VECTOR_BASE + IRQ_TIMER
|
||||
ProgramTimer(true, periodMs);
|
||||
|
||||
KernelLogStream(OK, "Timer") << "APIC timer started: initial count="
|
||||
<< base::dec << (uint64_t)CountForIntervalMs(periodMs);
|
||||
return true;
|
||||
}
|
||||
|
||||
void StartPeriodic(uint32_t periodMs) {
|
||||
ProgramTimer(true, periodMs);
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
uint32_t lvt = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_LVT);
|
||||
if ((lvt & LVT_MASKED) == 0) {
|
||||
Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT,
|
||||
lvt | LVT_MASKED);
|
||||
}
|
||||
}
|
||||
};
|
||||
File renamed without changes.
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Context.cpp
|
||||
* x86_64 thread context (implements Arch/Context.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Context.hpp>
|
||||
#include <Arch/x86_64/MSR.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
// Context.asm: save callee-saved registers + RSP + FPU, switch CR3, restore.
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
|
||||
uint8_t* oldFpuArea, uint8_t* newFpuArea);
|
||||
|
||||
// SyscallEntry.asm: jump to user mode via IRETQ.
|
||||
// `arg` is delivered as the user-mode RDI (SystemV first argument).
|
||||
extern "C" [[noreturn]] void JumpToUserMode(uint64_t rip, uint64_t rsp, uint64_t arg);
|
||||
|
||||
namespace Arch {
|
||||
|
||||
static constexpr uint32_t IA32_FS_BASE = 0xC0000100;
|
||||
|
||||
void InitFpuState(uint8_t* area) {
|
||||
memset(area, 0, FpuStateSize);
|
||||
*(uint16_t*)&area[0] = 0x037F; // FCW: default x87 control word
|
||||
*(uint32_t*)&area[24] = 0x1F80; // MXCSR: default SSE control/status
|
||||
}
|
||||
|
||||
uint64_t PrepareThreadStack(uint64_t kernelStackTop, void (*entry)()) {
|
||||
// The frame Context.asm pops: r15, r14, r13, r12, rbx, rbp, then the
|
||||
// return address it RETs to.
|
||||
uint64_t* sp = (uint64_t*)kernelStackTop;
|
||||
*(--sp) = (uint64_t)entry; // return addr
|
||||
*(--sp) = 0; // rbp
|
||||
*(--sp) = 0; // rbx
|
||||
*(--sp) = 0; // r12
|
||||
*(--sp) = 0; // r13
|
||||
*(--sp) = 0; // r14
|
||||
*(--sp) = 0; // r15
|
||||
return (uint64_t)sp;
|
||||
}
|
||||
|
||||
void SwitchContext(uint64_t* oldSp, uint64_t newSp, uint64_t newRoot,
|
||||
uint8_t* oldFpu, uint8_t* newFpu) {
|
||||
SchedContextSwitch(oldSp, newSp, newRoot, oldFpu, newFpu);
|
||||
}
|
||||
|
||||
void ActivateThread(Smp::CpuData* cpu, uint64_t kernelStackTop,
|
||||
uint64_t threadPointer) {
|
||||
// Kernel RSP for SYSCALL entry
|
||||
cpu->entry.kernelRsp = kernelStackTop;
|
||||
|
||||
// TSS RSP0 for hardware interrupts from ring 3
|
||||
cpu->arch.tss->rsp0 = kernelStackTop;
|
||||
|
||||
// The TLS thread pointer is IA32_FS_BASE. Cached per CPU: the MSR
|
||||
// write is skipped when unchanged, so TLS-free processes (pointer 0)
|
||||
// cost one write per CPU ever. Userspace cannot move FS base itself
|
||||
// (CR4.FSGSBASE is off), so the cache cannot go stale.
|
||||
if (cpu->arch.currentFsBase != threadPointer) {
|
||||
cpu->arch.currentFsBase = threadPointer;
|
||||
Hal::WriteMSR(IA32_FS_BASE, threadPointer);
|
||||
}
|
||||
}
|
||||
|
||||
void EnterUserMode(uint64_t entry, uint64_t userSp, uint64_t arg) {
|
||||
JumpToUserMode(entry, userSp, arg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Context.hpp
|
||||
* x86_64 thread context constants (see Arch/Context.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
// FXSAVE image.
|
||||
constexpr uint64_t FpuStateSize = 512;
|
||||
constexpr uint64_t FpuStateAlign = 16;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Cpu.hpp
|
||||
* x86_64 implementation of the Arch/Cpu.hpp primitives
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// RFLAGS.IF
|
||||
static constexpr uint64_t RflagsInterruptEnable = 1ULL << 9;
|
||||
|
||||
inline void Pause() {
|
||||
asm volatile("pause" ::: "memory");
|
||||
}
|
||||
|
||||
inline void StoreFence() {
|
||||
asm volatile("sfence" ::: "memory");
|
||||
}
|
||||
|
||||
inline void MemoryFence() {
|
||||
asm volatile("mfence" ::: "memory");
|
||||
}
|
||||
|
||||
inline void EnableInterrupts() {
|
||||
asm volatile("sti" ::: "memory");
|
||||
}
|
||||
|
||||
inline void DisableInterrupts() {
|
||||
asm volatile("cli" ::: "memory");
|
||||
}
|
||||
|
||||
inline bool InterruptsEnabled() {
|
||||
uint64_t flags;
|
||||
asm volatile("pushfq; pop %0" : "=r"(flags));
|
||||
return (flags & RflagsInterruptEnable) != 0;
|
||||
}
|
||||
|
||||
inline IrqFlags SaveAndDisableInterrupts() {
|
||||
uint64_t flags;
|
||||
asm volatile("pushfq; pop %0; cli" : "=r"(flags) :: "memory");
|
||||
return flags;
|
||||
}
|
||||
|
||||
inline void RestoreInterrupts(IrqFlags flags) {
|
||||
asm volatile("push %0; popfq" :: "r"(flags) : "memory", "cc");
|
||||
}
|
||||
|
||||
inline void Halt() {
|
||||
asm volatile("hlt" ::: "memory");
|
||||
}
|
||||
|
||||
// STI's one-instruction interrupt shadow makes STI;HLT atomic.
|
||||
inline void HaltWithInterruptsDisabled() {
|
||||
asm volatile("sti\n\thlt\n\tcli" ::: "memory");
|
||||
}
|
||||
|
||||
[[noreturn]] inline void HaltForever() {
|
||||
for (;;) {
|
||||
asm volatile("cli; hlt" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
inline uint64_t ReadTimestamp() {
|
||||
uint32_t lo, hi;
|
||||
asm volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
return ((uint64_t)hi << 32) | lo;
|
||||
}
|
||||
|
||||
// A write to the POST diagnostic port 0x80 is an ISA-bus cycle of roughly
|
||||
// a microsecond on every chipset since the 486, and never shorter.
|
||||
inline void IoDelay() {
|
||||
asm volatile("outb %%al, $0x80" :: "a"(0) : "memory");
|
||||
}
|
||||
|
||||
inline void WritebackInvalidateCaches() {
|
||||
asm volatile("wbinvd" ::: "memory");
|
||||
}
|
||||
|
||||
inline void FlushTlbPage(uint64_t virtualAddress) {
|
||||
asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory");
|
||||
}
|
||||
|
||||
inline void FlushTlbAll() {
|
||||
asm volatile("mov %%cr3, %%rax; mov %%rax, %%cr3" ::: "rax", "memory");
|
||||
}
|
||||
|
||||
inline uint64_t ReadPageTableRoot() {
|
||||
uint64_t cr3;
|
||||
asm volatile("mov %%cr3, %0" : "=r"(cr3));
|
||||
return cr3;
|
||||
}
|
||||
|
||||
inline void WritePageTableRoot(uint64_t rootPhys) {
|
||||
asm volatile("mov %0, %%cr3" :: "r"(rootPhys) : "memory");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Cpu.hpp
|
||||
* CPU feature enablement helpers
|
||||
* CpuFeatures.hpp
|
||||
* x86_64 CPU feature enablement and MWAIT idle helpers
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -32,14 +32,11 @@ namespace Hal {
|
||||
asm volatile("mwait" :: "a"(hint), "c"(0));
|
||||
}
|
||||
|
||||
// Atomic idle entry for paths that have already disabled interrupts.
|
||||
// Keeping STI immediately adjacent to HLT/MWAIT avoids the classic race
|
||||
// where an IRQ is handled between enabling interrupts and entering idle,
|
||||
// after which the CPU sleeps until the next timer deadline.
|
||||
inline void HaltWithInterruptsDisabled() {
|
||||
asm volatile("sti\n\thlt\n\tcli" ::: "memory");
|
||||
}
|
||||
|
||||
// Atomic idle entry for paths that have already disabled interrupts
|
||||
// (the MWAIT counterpart of Arch::HaltWithInterruptsDisabled). Keeping
|
||||
// STI immediately adjacent to MWAIT avoids the classic race where an IRQ
|
||||
// is handled between enabling interrupts and entering idle, after which
|
||||
// the CPU sleeps until the next timer deadline.
|
||||
inline void IdleWaitWithInterruptsDisabled(volatile uint64_t* monitorAddr,
|
||||
uint32_t hint = 0) {
|
||||
asm volatile("monitor" :: "a"(monitorAddr), "c"(0), "d"(0) : "memory");
|
||||
@@ -30,16 +30,19 @@
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "CpuPower.hpp"
|
||||
#include "MSR.hpp"
|
||||
#include "SmpBoot.hpp"
|
||||
#include <Arch/CpuPower.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Arch/x86_64/MSR.hpp>
|
||||
#include <Arch/x86_64/Acpi/CpuIdle.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <atomic>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
namespace Arch {
|
||||
namespace CpuPower {
|
||||
using Hal::ReadMSR;
|
||||
using Hal::WriteMSR;
|
||||
|
||||
// ============================================================
|
||||
// MSR addresses (power/thermal; used only in this module)
|
||||
@@ -399,6 +402,7 @@ namespace Hal {
|
||||
out.BaseMHz = g_baseMHz;
|
||||
out.MaxMHz = g_maxMHz;
|
||||
out.EffMHz = g_effMHz.load(std::memory_order_relaxed);
|
||||
out.ApIdleHint = Hal::CpuIdle::GetApIdleHint();
|
||||
return true;
|
||||
}
|
||||
|
||||
File renamed without changes.
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "GDT.hpp"
|
||||
#include "../Terminal/Terminal.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
namespace Hal {
|
||||
File renamed without changes.
@@ -9,14 +9,14 @@
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Common/Panic.hpp>
|
||||
#include <Common/TerminationCodes.hpp>
|
||||
#include <Platform/Registers.hpp>
|
||||
#include <Arch/x86_64/InterruptFrame.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Sched/CrashReport.hpp>
|
||||
#include <Api/Heap.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
|
||||
namespace Hal {
|
||||
constexpr auto InterruptGate = 0x8E;
|
||||
@@ -59,6 +59,20 @@ namespace Hal {
|
||||
"Reserved"
|
||||
};
|
||||
|
||||
// Termination code for a user-mode CPU exception. The exact vector is
|
||||
// kept in the crash report; this is only the category.
|
||||
static TerminationCodes TerminationFromVector(uint8_t vector) {
|
||||
switch (vector) {
|
||||
case 0x06: return TerminatedIllegalInstruction; // #UD
|
||||
case 0x01: // #DB
|
||||
case 0x03: return TerminatedBreakpoint; // #BP
|
||||
case 0x00: // #DE
|
||||
case 0x10: // #MF
|
||||
case 0x13: return TerminatedArithmeticError; // #XM
|
||||
default: return TerminatedAccessViolation; // #PF, #GP, #SS, #NP, #AC, #OF, #BR, #CP, ...
|
||||
}
|
||||
}
|
||||
|
||||
// Exceptions that push a hardware error code before IP/CS/FLAGS/SP/SS
|
||||
static bool ExceptionHasErrorCode(uint8_t vector) {
|
||||
return vector == 8 || (vector >= 10 && vector <= 14)
|
||||
@@ -67,16 +81,16 @@ namespace Hal {
|
||||
|
||||
// Extract CS from the interrupt frame. For error-code exceptions, the
|
||||
// error code sits at offset 0, shifting IP to +8 and CS to +16.
|
||||
static uint64_t GetExceptionCS(uint8_t vector, System::PanicFrame* frame) {
|
||||
static uint64_t GetExceptionCS(uint8_t vector, Arch::PanicFrame* frame) {
|
||||
if (ExceptionHasErrorCode(vector)) {
|
||||
return *(uint64_t*)((uint8_t*)frame + 16);
|
||||
}
|
||||
return frame->CS;
|
||||
}
|
||||
|
||||
static System::PanicFrame* GetExceptionRegs(uint8_t vector, System::PanicFrame* frame) {
|
||||
static Arch::PanicFrame* GetExceptionRegs(uint8_t vector, Arch::PanicFrame* frame) {
|
||||
if (ExceptionHasErrorCode(vector)) {
|
||||
return (System::PanicFrame*)((uint8_t*)frame + sizeof(uint64_t));
|
||||
return (Arch::PanicFrame*)((uint8_t*)frame + sizeof(uint64_t));
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
@@ -84,7 +98,7 @@ namespace Hal {
|
||||
// Shared fatal-exception path: kill the faulting user process (with a
|
||||
// crash report) or panic the kernel. `frame` is the RAW interrupt frame
|
||||
// (error code at offset 0 for vectors that push one). Never returns.
|
||||
static void HandleFatalException(uint8_t i, System::PanicFrame* frame)
|
||||
static void HandleFatalException(uint8_t i, Arch::PanicFrame* frame)
|
||||
{
|
||||
uint64_t cs = GetExceptionCS(i, frame);
|
||||
bool fromUser = (cs & 3) == 3;
|
||||
@@ -136,7 +150,7 @@ namespace Hal {
|
||||
constexpr uint64_t snapshotBytes =
|
||||
CrashReport::StackSnapshotWords * sizeof(uint64_t);
|
||||
if (Memory::VMM::Paging::IsUserRangeAccessible(
|
||||
proc->pml4Phys, regs->SP, snapshotBytes, false)) {
|
||||
proc->pageTableRoot, regs->SP, snapshotBytes, false)) {
|
||||
auto* userStack = (const volatile uint64_t*)regs->SP;
|
||||
for (int word = 0; word < CrashReport::StackSnapshotWords; word++)
|
||||
rep.stackWords[word] = userStack[word];
|
||||
@@ -146,7 +160,7 @@ namespace Hal {
|
||||
if (i == 0x0E) {
|
||||
// Page fault: read CR2 for faulting address
|
||||
asm volatile("mov %%cr2, %0" : "=r"(rep.faultingAddress));
|
||||
auto* pf = (System::PageFaultPanicFrame*)frame;
|
||||
auto* pf = (Arch::PageFaultPanicFrame*)frame;
|
||||
rep.pfPresent = pf->PageFaultError.Present;
|
||||
rep.pfWrite = pf->PageFaultError.Write;
|
||||
rep.pfUser = pf->PageFaultError.User;
|
||||
@@ -201,7 +215,7 @@ namespace Hal {
|
||||
}
|
||||
|
||||
template<size_t i>
|
||||
__attribute__((interrupt)) void ExceptionHandler(System::PanicFrame* frame)
|
||||
__attribute__((interrupt)) void ExceptionHandler(Arch::PanicFrame* frame)
|
||||
{
|
||||
HandleFatalException(i, frame);
|
||||
}
|
||||
@@ -212,7 +226,7 @@ namespace Hal {
|
||||
// user stack growth region maps a fresh zeroed page and retries the
|
||||
// faulting instruction. With the two-argument form, `frame` points past
|
||||
// the error code, directly at the saved IP.
|
||||
__attribute__((interrupt)) void PageFaultHandler(System::PanicFrame* frame, uint64_t errorCode)
|
||||
__attribute__((interrupt)) void PageFaultHandler(Arch::PanicFrame* frame, uint64_t errorCode)
|
||||
{
|
||||
bool fromUser = (frame->CS & 3) == 3;
|
||||
if (fromUser) asm volatile("swapgs");
|
||||
@@ -226,7 +240,10 @@ namespace Hal {
|
||||
// user stack buffers passed into syscalls.
|
||||
if ((errorCode & 1) == 0 && cpu != nullptr && cpu->currentSlot >= 0
|
||||
&& Sched::GetCurrentPid() >= 0) {
|
||||
if (montauk::abi::TryHandleAnonymousPageFault(cr2, errorCode) ||
|
||||
// Error code bit 1 = write, bit 4 = instruction fetch.
|
||||
Memory::VMM::PageFault fault{false, (errorCode & (1ULL << 1)) != 0,
|
||||
(errorCode & (1ULL << 4)) != 0};
|
||||
if (montauk::abi::TryHandleAnonymousPageFault(cr2, fault) ||
|
||||
Sched::TryGrowUserStack(cr2)) {
|
||||
if (fromUser) asm volatile("swapgs");
|
||||
return;
|
||||
@@ -236,7 +253,7 @@ namespace Hal {
|
||||
// Not a growable fault. Hand the RAW frame (error code at offset 0)
|
||||
// to the fatal path, which re-derives fromUser and swaps GS itself.
|
||||
if (fromUser) asm volatile("swapgs");
|
||||
HandleFatalException(0x0E, (System::PanicFrame*)((uint8_t*)frame - 8));
|
||||
HandleFatalException(0x0E, (Arch::PanicFrame*)((uint8_t*)frame - 8));
|
||||
}
|
||||
|
||||
void LoadIDT(IDTRStruct& idtr) {
|
||||
File renamed without changes.
@@ -1,10 +1,15 @@
|
||||
/*
|
||||
* InterruptFrame.hpp
|
||||
* x86_64 exception frames as pushed by the CPU
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#if defined (__x86_64__)
|
||||
namespace System {
|
||||
namespace Arch {
|
||||
struct PanicFrame {
|
||||
uint64_t IP;
|
||||
uint64_t CS;
|
||||
@@ -51,4 +56,3 @@ namespace System {
|
||||
}__attribute__((packed));
|
||||
|
||||
};
|
||||
#endif
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* IoPort.hpp
|
||||
* x86_64 port I/O (implements Io/IoPort.hpp)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Io {
|
||||
static constexpr bool HasPortIo = true;
|
||||
|
||||
// OSDev website has the parameters for out(b/w/l) in the opposite order in their example,
|
||||
// (port, then value), but the manpage on GNU ('man outb') has it in this order.
|
||||
|
||||
inline void Out8(uint8_t value, uint16_t port) {
|
||||
asm ("outb %0, %1" : : "a"(value), "Nd"(port) : "memory");
|
||||
}
|
||||
|
||||
inline void Out16(uint16_t value, uint16_t port) {
|
||||
asm ("outw %0, %1" : : "a"(value), "Nd"(port) : "memory");
|
||||
}
|
||||
|
||||
inline void Out32(uint32_t value, uint16_t port) {
|
||||
asm ("outl %0, %1" : : "a"(value), "Nd"(port) : "memory");
|
||||
}
|
||||
|
||||
inline uint8_t In8(uint16_t port) {
|
||||
uint8_t result;
|
||||
asm volatile("inb %1, %0" : "=a"(result) : "Nd"(port) : "memory");
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint16_t In16(uint16_t port) {
|
||||
uint16_t result;
|
||||
asm volatile("inw %1, %0" : "=a"(result) : "Nd"(port) : "memory");
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32_t In32(uint16_t port) {
|
||||
uint32_t result;
|
||||
asm volatile("inl %1, %0" : "=a"(result) : "Nd"(port) : "memory");
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void IoPortWait() {
|
||||
Out8(0, 0x80);
|
||||
}
|
||||
};
|
||||
File renamed without changes.
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* PageTable.hpp
|
||||
* x86_64 4-level page-table structures
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace Memory::VMM {
|
||||
struct PageTableEntry {
|
||||
std::uint8_t Present : 1;
|
||||
std::uint8_t Writable : 1;
|
||||
std::uint8_t Supervisor : 1;
|
||||
std::uint8_t WriteThrough : 1;
|
||||
std::uint8_t CacheDisabled : 1;
|
||||
std::uint8_t Accessed : 1;
|
||||
std::uint8_t Ignore : 1;
|
||||
std::uint8_t LargerPages : 1;
|
||||
std::uint8_t PageSize : 1;
|
||||
std::uint8_t Available : 3;
|
||||
std::uint64_t Address : 52;
|
||||
};
|
||||
|
||||
struct PageTableEntry40Bit {
|
||||
std::uint8_t Present : 1;
|
||||
std::uint8_t Writable : 1;
|
||||
std::uint8_t Supervisor : 1;
|
||||
std::uint8_t WriteThrough : 1;
|
||||
std::uint8_t CacheDisabled : 1;
|
||||
std::uint8_t Accessed : 1;
|
||||
std::uint8_t Ignore : 1;
|
||||
std::uint8_t LargerPages : 1;
|
||||
std::uint8_t PageSize : 1;
|
||||
std::uint8_t Available : 3;
|
||||
std::uint64_t Address : 40;
|
||||
std::uint8_t AvailableHigh : 7;
|
||||
std::uint8_t PK : 4;
|
||||
std::uint8_t NX : 1;
|
||||
};
|
||||
|
||||
|
||||
struct PageTable {
|
||||
PageTableEntry entries[512];
|
||||
} __attribute__((packed)) __attribute__((aligned(0x1000)));
|
||||
|
||||
struct VirtualAddress {
|
||||
std::uint64_t address;
|
||||
|
||||
// Page-table index extraction works for any byte address. Callers that
|
||||
// truly require page alignment validate that explicitly before mapping.
|
||||
VirtualAddress(std::uint64_t newAddress) : address(newAddress) {}
|
||||
|
||||
uint64_t GetL4Index() {
|
||||
return (address >> 39) & 0x1ff;
|
||||
}
|
||||
|
||||
uint64_t GetL3Index() {
|
||||
return (address >> 30) & 0x1ff;
|
||||
}
|
||||
|
||||
uint64_t GetL2Index() {
|
||||
return (address >> 21) & 0x1ff;
|
||||
}
|
||||
|
||||
uint64_t GetPageIndex() {
|
||||
return (address >> 12) & 0x1ff;
|
||||
}
|
||||
|
||||
uint64_t GetIndex(size_t level) {
|
||||
if (level == 4)
|
||||
return GetL4Index();
|
||||
|
||||
else if (level == 3)
|
||||
return GetL3Index();
|
||||
|
||||
else if (level == 2)
|
||||
return GetL2Index();
|
||||
|
||||
else if (level == 1)
|
||||
return GetPageIndex();
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,12 @@
|
||||
#include "Paging.hpp"
|
||||
/*
|
||||
* Paging.cpp
|
||||
* x86_64 4-level paging (implements Memory/Paging.hpp)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/x86_64/PageTable.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Common/Panic.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
@@ -51,12 +59,22 @@ namespace Memory::VMM {
|
||||
|
||||
extern "C" uint64_t KernelStartSymbol;
|
||||
|
||||
static std::uint64_t GetPhysAddrImpl(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1);
|
||||
|
||||
std::uint64_t GetPhysKernelAddress(std::uint64_t virtualAddress) {
|
||||
return Paging::GetPhysAddr(GetCR3(), (std::uint64_t)virtualAddress, true);
|
||||
return GetPhysAddrImpl(Arch::ReadPageTableRoot(), (std::uint64_t)virtualAddress, true);
|
||||
}
|
||||
|
||||
void ActivateKernelAddressSpace() {
|
||||
Arch::WritePageTableRoot(g_paging->KernelRoot());
|
||||
}
|
||||
|
||||
void FlushTLB() {
|
||||
Arch::FlushTlbAll();
|
||||
}
|
||||
|
||||
Paging::Paging() {
|
||||
PML4 = (PageTable*)SubHHDM((PageTable*)Memory::g_pfa->AllocateZeroed());
|
||||
rootPhys = SubHHDM(Memory::g_pfa->AllocateZeroed());
|
||||
}
|
||||
|
||||
void Paging::Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, const montauk::boot::MemoryMap& memMap,
|
||||
@@ -120,11 +138,11 @@ namespace Memory::VMM {
|
||||
}
|
||||
}
|
||||
|
||||
LoadCR3(PML4);
|
||||
Arch::WritePageTableRoot(rootPhys);
|
||||
Kt::KernelLogStream(Kt::OK, "VMM") << "Switched CR3";
|
||||
}
|
||||
|
||||
PageTable* Paging::HandleLevel(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
|
||||
static PageTable* HandleLevel(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
|
||||
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]);
|
||||
|
||||
if (!entry->Present) {
|
||||
@@ -144,7 +162,7 @@ namespace Memory::VMM {
|
||||
}
|
||||
}
|
||||
|
||||
PageTable* Paging::HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
|
||||
static PageTable* HandleLevelUser(VirtualAddress virtualAddress, PageTable* table, const size_t level) {
|
||||
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[virtualAddress.GetIndex(level)]);
|
||||
|
||||
if (!entry->Present) {
|
||||
@@ -174,7 +192,7 @@ namespace Memory::VMM {
|
||||
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
auto PML3 = HandleLevel(virtualAddressObj, PML4, 4);
|
||||
auto PML3 = HandleLevel(virtualAddressObj, (PageTable*)rootPhys, 4);
|
||||
auto PML2 = HandleLevel(virtualAddressObj, PML3, 3);
|
||||
auto PML1 = HandleLevel(virtualAddressObj, PML2, 2);
|
||||
|
||||
@@ -190,7 +208,7 @@ namespace Memory::VMM {
|
||||
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
auto PML3 = HandleLevel(virtualAddressObj, PML4, 4);
|
||||
auto PML3 = HandleLevel(virtualAddressObj, (PageTable*)rootPhys, 4);
|
||||
auto PML2 = HandleLevel(virtualAddressObj, PML3, 3);
|
||||
auto PML1 = HandleLevel(virtualAddressObj, PML2, 2);
|
||||
|
||||
@@ -206,7 +224,7 @@ namespace Memory::VMM {
|
||||
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
auto PML3 = HandleLevel(virtualAddressObj, PML4, 4);
|
||||
auto PML3 = HandleLevel(virtualAddressObj, (PageTable*)rootPhys, 4);
|
||||
auto PML2 = HandleLevel(virtualAddressObj, PML3, 3);
|
||||
auto PML1 = HandleLevel(virtualAddressObj, PML2, 2);
|
||||
|
||||
@@ -216,7 +234,7 @@ namespace Memory::VMM {
|
||||
|
||||
// The page may have been mapped before with other cache attributes
|
||||
// (an HHDM page remapped uncached): drop any stale translation.
|
||||
asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory");
|
||||
Arch::FlushTlbPage(virtualAddress);
|
||||
}
|
||||
|
||||
void Paging::MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
@@ -226,7 +244,7 @@ namespace Memory::VMM {
|
||||
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
auto PML3 = HandleLevelUser(virtualAddressObj, PML4, 4);
|
||||
auto PML3 = HandleLevelUser(virtualAddressObj, (PageTable*)rootPhys, 4);
|
||||
auto PML2 = HandleLevelUser(virtualAddressObj, PML3, 3);
|
||||
auto PML1 = HandleLevelUser(virtualAddressObj, PML2, 2);
|
||||
|
||||
@@ -235,14 +253,14 @@ namespace Memory::VMM {
|
||||
SetLeafPte(pageEntry, physicalAddress, true, false, false);
|
||||
}
|
||||
|
||||
std::uint64_t Paging::CreateUserPML4() {
|
||||
std::uint64_t Paging::CreateUserAddressSpace() {
|
||||
// Allocate a new PML4
|
||||
void* newPage = Memory::g_pfa->AllocateZeroed();
|
||||
uint64_t newPml4Phys = Memory::SubHHDM((uint64_t)newPage);
|
||||
PageTable* newPml4 = (PageTable*)newPage; // HHDM virtual address
|
||||
|
||||
// Copy kernel-half entries (256-511) from the global PML4
|
||||
PageTable* kernelPml4 = (PageTable*)Memory::HHDM((uint64_t)g_paging->PML4);
|
||||
PageTable* kernelPml4 = (PageTable*)Memory::HHDM(g_paging->KernelRoot());
|
||||
for (int i = 256; i < 512; i++) {
|
||||
newPml4->entries[i] = kernelPml4->entries[i];
|
||||
}
|
||||
@@ -250,11 +268,11 @@ namespace Memory::VMM {
|
||||
return newPml4Phys;
|
||||
}
|
||||
|
||||
bool Paging::MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
return MapUserInPermissions(pml4Phys, physicalAddress, virtualAddress, true, true);
|
||||
bool Paging::MapUserIn(std::uint64_t pageTableRoot, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
return MapUserInPermissions(pageTableRoot, physicalAddress, virtualAddress, true, true);
|
||||
}
|
||||
|
||||
bool Paging::MapUserInPermissions(std::uint64_t pml4Phys, std::uint64_t physicalAddress,
|
||||
bool Paging::MapUserInPermissions(std::uint64_t pageTableRoot, std::uint64_t physicalAddress,
|
||||
std::uint64_t virtualAddress, bool writable,
|
||||
bool executable) {
|
||||
pagingLock.Acquire();
|
||||
@@ -284,7 +302,7 @@ namespace Memory::VMM {
|
||||
}
|
||||
};
|
||||
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
PageTable* pml4 = (PageTable*)pageTableRoot;
|
||||
auto pml3 = walkLevel(pml4, va.GetL4Index());
|
||||
if (!pml3) { pagingLock.Release(); return false; }
|
||||
auto pml2 = walkLevel(pml3, va.GetL3Index());
|
||||
@@ -298,7 +316,7 @@ namespace Memory::VMM {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Paging::ProtectUserIn(std::uint64_t pml4Phys, std::uint64_t virtualAddress,
|
||||
bool Paging::ProtectUserIn(std::uint64_t pageTableRoot, std::uint64_t virtualAddress,
|
||||
bool writable, bool executable) {
|
||||
if ((virtualAddress & 0xFFFULL) != 0) return false;
|
||||
pagingLock.Acquire();
|
||||
@@ -310,7 +328,7 @@ namespace Memory::VMM {
|
||||
return (PageTable*)(entry->Address << 12);
|
||||
};
|
||||
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
PageTable* pml4 = (PageTable*)pageTableRoot;
|
||||
auto pml3 = walkRead(pml4, va.GetL4Index());
|
||||
if (!pml3) { pagingLock.Release(); return false; }
|
||||
auto pml2 = walkRead(pml3, va.GetL3Index());
|
||||
@@ -329,12 +347,12 @@ namespace Memory::VMM {
|
||||
if (executable) raw &= ~LeafNoExecute;
|
||||
else raw |= LeafNoExecute;
|
||||
*(volatile uint64_t*)pte = raw;
|
||||
asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory");
|
||||
Arch::FlushTlbPage(virtualAddress);
|
||||
pagingLock.Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Paging::MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
bool Paging::MapUserInWC(std::uint64_t pageTableRoot, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
|
||||
pagingLock.Acquire();
|
||||
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
|
||||
pagingLock.Release();
|
||||
@@ -360,7 +378,7 @@ namespace Memory::VMM {
|
||||
}
|
||||
};
|
||||
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
PageTable* pml4 = (PageTable*)pageTableRoot;
|
||||
auto pml3 = walkLevel(pml4, va.GetL4Index());
|
||||
if (!pml3) { pagingLock.Release(); return false; }
|
||||
auto pml2 = walkLevel(pml3, va.GetL3Index());
|
||||
@@ -374,7 +392,7 @@ namespace Memory::VMM {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Paging::UnmapUserIn(std::uint64_t pml4Phys, std::uint64_t virtualAddress) {
|
||||
void Paging::UnmapUserIn(std::uint64_t pageTableRoot, std::uint64_t virtualAddress) {
|
||||
pagingLock.Acquire();
|
||||
VirtualAddress va(virtualAddress);
|
||||
|
||||
@@ -385,7 +403,7 @@ namespace Memory::VMM {
|
||||
return (PageTable*)(entry->Address << 12);
|
||||
};
|
||||
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
PageTable* pml4 = (PageTable*)pageTableRoot;
|
||||
auto pml3 = walkRead(pml4, va.GetL4Index());
|
||||
if (!pml3) { pagingLock.Release(); return; }
|
||||
auto pml2 = walkRead(pml3, va.GetL3Index());
|
||||
@@ -400,14 +418,14 @@ namespace Memory::VMM {
|
||||
*(uint64_t*)pageEntry = 0;
|
||||
|
||||
// Invalidate TLB for this virtual address
|
||||
asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory");
|
||||
Arch::FlushTlbPage(virtualAddress);
|
||||
pagingLock.Release();
|
||||
}
|
||||
|
||||
void Paging::FreeUserHalf(std::uint64_t pml4Phys) {
|
||||
void Paging::FreeUserHalf(std::uint64_t pageTableRoot) {
|
||||
// PageTable* pointers store PHYSICAL addresses (same convention as MapUserIn/UnmapUserIn).
|
||||
// Each individual entry access goes through HHDM() to get a valid virtual pointer.
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
PageTable* pml4 = (PageTable*)pageTableRoot;
|
||||
|
||||
// Walk user-half entries (0-255); kernel-half (256-511) is shared and must not be touched.
|
||||
for (int i4 = 0; i4 < 256; i4++) {
|
||||
@@ -459,7 +477,7 @@ namespace Memory::VMM {
|
||||
}
|
||||
}
|
||||
|
||||
bool Paging::IsUserRangeAccessible(std::uint64_t pml4Phys, std::uint64_t virtualAddress,
|
||||
bool Paging::IsUserRangeAccessible(std::uint64_t pageTableRoot, std::uint64_t virtualAddress,
|
||||
std::uint64_t size, bool requireWrite) {
|
||||
static constexpr uint64_t USER_SPACE_END = 0x0000800000000000ULL;
|
||||
|
||||
@@ -470,7 +488,7 @@ namespace Memory::VMM {
|
||||
|
||||
auto pageAccessible = [&](uint64_t pageVa) -> bool {
|
||||
VirtualAddress va(pageVa);
|
||||
PageTable* pml4 = (PageTable*)pml4Phys;
|
||||
PageTable* pml4 = (PageTable*)pageTableRoot;
|
||||
|
||||
PageTableEntry* pml4e = (PageTableEntry*)Memory::HHDM(&pml4->entries[va.GetL4Index()]);
|
||||
if (!pml4e->Present || !pml4e->Supervisor) return false;
|
||||
@@ -506,7 +524,13 @@ namespace Memory::VMM {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t root, std::uint64_t virtualAddress) {
|
||||
return GetPhysAddrImpl(root, virtualAddress, false);
|
||||
}
|
||||
|
||||
// use40BitL1 reads the leaf through the narrower historical layout; the
|
||||
// boot-time kernel-image walk over the bootloader's tables relies on it.
|
||||
static std::uint64_t GetPhysAddrImpl(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
|
||||
VirtualAddress virtualAddressObj(virtualAddress);
|
||||
|
||||
PageTable* pml4Virt = (PageTable*)HHDM(pml4);
|
||||
@@ -548,7 +572,7 @@ namespace Memory::VMM {
|
||||
}
|
||||
|
||||
std::uint64_t Paging::GetPhysAddr(std::uint64_t virtualAddress) {
|
||||
return GetPhysAddr((std::uint64_t)PML4, virtualAddress, false);
|
||||
return GetPhysAddrImpl(rootPhys, virtualAddress, false);
|
||||
}
|
||||
|
||||
void Paging::MapEfiRuntime(const montauk::boot::EfiInfo& efi) {
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* PanicFrame.cpp
|
||||
* x86_64 CPU state for the panic screen
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Common/Panic.hpp>
|
||||
#include <Arch/x86_64/InterruptFrame.hpp>
|
||||
#include <CppLib/BoxUI.hpp>
|
||||
|
||||
static constexpr int BoxWidth = PanicBoxWidth;
|
||||
|
||||
static void PrintPageFaultInfo(Arch::PanicFrame*& frame) {
|
||||
auto* pf = (Arch::PageFaultPanicFrame*)frame;
|
||||
frame = (Arch::PanicFrame*)&pf->IP;
|
||||
|
||||
uint64_t cr2;
|
||||
asm volatile("mov %%cr2, %0" : "=r"(cr2));
|
||||
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, BoxWidth);
|
||||
|
||||
PrintBoxedLine(kerr, "Page Fault Error:", BoxWidth, true);
|
||||
PrintBoxedDec(kerr, "Present", pf->PageFaultError.Present, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Write", pf->PageFaultError.Write, BoxWidth);
|
||||
PrintBoxedDec(kerr, "User", pf->PageFaultError.User, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Reserved Write", pf->PageFaultError.ReservedWrite, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Instruction Fetch",pf->PageFaultError.InstructionFetch, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Protection Key", pf->PageFaultError.ProtectionKey, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Shadow Stack", pf->PageFaultError.ShadowStack, BoxWidth);
|
||||
PrintBoxedDec(kerr, "SGX", pf->PageFaultError.SGX, BoxWidth);
|
||||
}
|
||||
|
||||
static void PrintGPFInfo(Arch::PanicFrame*& frame) {
|
||||
auto* gpf = (Arch::GPFPanicFrame*)frame;
|
||||
frame = (Arch::PanicFrame*)&gpf->IP;
|
||||
|
||||
PrintBoxedLine(kerr, "General Protection Fault:", BoxWidth, true);
|
||||
PrintBoxedDec(kerr, "Error Code", gpf->GeneralProtectionFaultError, BoxWidth);
|
||||
}
|
||||
|
||||
// Link-time base of the kernel image (see linker-x86_64.ld).
|
||||
static constexpr uint64_t KernelBaseVirt = 0xFFFFFFFF80000000ULL;
|
||||
|
||||
static void PrintRegisters(Arch::PanicFrame* frame) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, BoxWidth);
|
||||
|
||||
// The absolute address means nothing without the link map. Its offset from
|
||||
// the kernel's load base is what `nm bin-x86_64/kernel` can be searched
|
||||
// for directly, so print that next to it.
|
||||
if (frame->IP >= KernelBaseVirt)
|
||||
PrintBoxedHex(kerr, " kernel+", frame->IP - KernelBaseVirt, BoxWidth);
|
||||
|
||||
|
||||
PrintBoxedHex(kerr, "Code Segment", frame->CS, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Flags", frame->Flags, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Pointer", frame->SP, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Segment", frame->SS, BoxWidth);
|
||||
}
|
||||
|
||||
namespace Arch {
|
||||
void PrintPanicFrame(PanicFrame* frame) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "CPU State:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, BoxWidth);
|
||||
|
||||
if (frame->InterruptVector == 0xE)
|
||||
PrintPageFaultInfo(frame);
|
||||
else if (frame->InterruptVector == 0xD)
|
||||
PrintGPFInfo(frame);
|
||||
|
||||
PrintRegisters(frame);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* PerCpu.hpp
|
||||
* x86_64 per-CPU state (see Arch/Smp.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Arch/x86_64/GDT.hpp>
|
||||
|
||||
// ====================================================================
|
||||
// Assembly-visible offsets into Smp::CpuData
|
||||
// These MUST match the struct layout. Verified by static_assert in
|
||||
// SmpBoot.cpp; SyscallEntry.asm repeats them.
|
||||
// ====================================================================
|
||||
#define CPUDATA_SELF_PTR 0
|
||||
#define CPUDATA_KERNEL_RSP 8
|
||||
#define CPUDATA_USER_RSP 16
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Read by SyscallEntry.asm through GS.
|
||||
struct CpuEntryState {
|
||||
uint64_t kernelRsp; // kernel stack top for SYSCALL
|
||||
uint64_t userRspScratch; // scratch for saving user RSP
|
||||
};
|
||||
|
||||
struct CpuState {
|
||||
uint32_t lapicId; // local APIC ID
|
||||
bool hasMwait; // CPU supports MONITOR/MWAIT
|
||||
uint64_t currentFsBase = ~0ULL; // last IA32_FS_BASE written on this CPU
|
||||
|
||||
Hal::TSS64* tss; // this CPU's TSS (BSP: the global one)
|
||||
|
||||
// Per-CPU GDT and TSS (APs use these; BSP uses globals)
|
||||
Hal::BasicGDT cpuGdt __attribute__((aligned(16)));
|
||||
Hal::TSS64 cpuTss __attribute__((aligned(16)));
|
||||
};
|
||||
|
||||
// GS base holds the CpuData pointer in kernel mode; gs:0 is its selfPtr.
|
||||
inline void* CurrentCpuLocal() {
|
||||
void* ptr;
|
||||
asm volatile("mov %%gs:0, %0" : "=r"(ptr));
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Read IA32_GS_BASE directly rather than gs:0, which would fault before
|
||||
// InitBsp() has installed a GS base and turn the original, diagnosable
|
||||
// exception into a triple fault.
|
||||
inline void* TryCurrentCpuLocal() {
|
||||
uint32_t low;
|
||||
uint32_t high;
|
||||
asm volatile("rdmsr" : "=a"(low), "=d"(high) : "c"(0xC0000101u));
|
||||
return (void*)(((uint64_t)high << 32) | low);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Platform.cpp
|
||||
* x86_64 bring-up hooks (implements Arch/Platform.hpp)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Platform.hpp>
|
||||
#include <Arch/CpuPower.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Arch/x86_64/GDT.hpp>
|
||||
#include <Arch/x86_64/IDT.hpp>
|
||||
#include <Arch/x86_64/MSR.hpp>
|
||||
#include <Arch/x86_64/CpuFeatures.hpp>
|
||||
#include <Arch/x86_64/Apic/ApicInit.hpp>
|
||||
#include <Arch/x86_64/Acpi/CpuIdle.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Drivers/PS2/Keyboard.hpp>
|
||||
#include <Drivers/PS2/Mouse.hpp>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
void EarlyInitialize() {
|
||||
Hal::PrepareGDT();
|
||||
Hal::BridgeLoadGDT();
|
||||
|
||||
Hal::EnableSSE();
|
||||
}
|
||||
|
||||
void InitializeTraps() {
|
||||
Hal::IDTInitialize();
|
||||
}
|
||||
|
||||
void InitializeMemoryAttributes() {
|
||||
Hal::InitializePAT();
|
||||
}
|
||||
|
||||
void InitializeInterrupts(Hal::ACPI::CommonSDTHeader* xsdt) {
|
||||
Hal::CpuIdle::Initialize(xsdt);
|
||||
|
||||
Hal::ApicInitialize(xsdt);
|
||||
|
||||
Smp::InitBsp();
|
||||
|
||||
CpuPower::InitializeBsp();
|
||||
}
|
||||
|
||||
void InitializePlatformDevices() {
|
||||
Drivers::PS2::Initialize();
|
||||
Drivers::PS2::Keyboard::Initialize();
|
||||
Drivers::PS2::Mouse::Initialize();
|
||||
}
|
||||
|
||||
void PrepareUserMode() {
|
||||
Hal::LoadTSS();
|
||||
}
|
||||
|
||||
void LateInitialize() {
|
||||
Drivers::PS2::Mouse::FlushState();
|
||||
}
|
||||
|
||||
void ListPlatformDevices(PlatformDeviceSink sink, void* context) {
|
||||
sink(context, 1, "Local APIC", "");
|
||||
sink(context, 1, "I/O APIC", "");
|
||||
|
||||
sink(context, 2, "LAPIC Timer", "Local APIC periodic timer");
|
||||
|
||||
sink(context, 3, "PS/2 Keyboard", "IRQ 1");
|
||||
if (Drivers::PS2::IsDualChannel()) {
|
||||
sink(context, 3, "PS/2 Mouse", "IRQ 12");
|
||||
}
|
||||
}
|
||||
|
||||
void Reboot() {
|
||||
// Triple fault: load an empty IDT and raise an exception.
|
||||
struct [[gnu::packed]] { uint16_t limit; uint64_t base; } nullIdt = {0, 0};
|
||||
asm volatile("lidt %0; int $0x03" :: "m"(nullIdt));
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,30 +4,30 @@
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "SmpBoot.hpp"
|
||||
#include <ACPI/CpuIdle.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/IDT.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
#include <Arch/x86_64/Acpi/CpuIdle.hpp>
|
||||
#include <Arch/x86_64/Apic/Apic.hpp>
|
||||
#include <Arch/x86_64/Apic/ApicInit.hpp>
|
||||
#include <Arch/x86_64/Apic/Interrupts.hpp>
|
||||
#include <Arch/x86_64/IDT.hpp>
|
||||
#include <Arch/x86_64/MSR.hpp>
|
||||
#include <Arch/x86_64/CpuFeatures.hpp>
|
||||
#include <Arch/CpuPower.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/Syscall.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
#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();
|
||||
static_assert(__builtin_offsetof(Smp::CpuData, selfPtr) == CPUDATA_SELF_PTR, "CpuData offset mismatch: selfPtr");
|
||||
static_assert(__builtin_offsetof(Smp::CpuData, entry.kernelRsp) == CPUDATA_KERNEL_RSP, "CpuData offset mismatch: kernelRsp");
|
||||
static_assert(__builtin_offsetof(Smp::CpuData, entry.userRspScratch) == CPUDATA_USER_RSP, "CpuData offset mismatch: userRspScratch");
|
||||
|
||||
// Assembly helpers (GDT.asm)
|
||||
extern "C" void LoadGDT(Hal::GDTPointer* ptr);
|
||||
@@ -50,25 +50,29 @@ namespace Smp {
|
||||
return g_cpuCount;
|
||||
}
|
||||
|
||||
int GetDetectedCpuCount() {
|
||||
return Hal::GetDetectedCpuCount();
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// 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);
|
||||
memset(&cpu.arch.cpuTss, 0, sizeof(Hal::TSS64));
|
||||
cpu.arch.cpuTss.iopbOffset = sizeof(Hal::TSS64);
|
||||
|
||||
// Allocate a 4KB IST1 stack for Double Fault and NMI.
|
||||
// These exceptions need a known-good stack to avoid triple
|
||||
// faults when the normal kernel stack overflows.
|
||||
void* istPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (istPage) {
|
||||
cpu.cpuTss.ist1 = (uint64_t)istPage + 0x1000; // top of stack
|
||||
cpu.arch.cpuTss.ist1 = (uint64_t)istPage + 0x1000; // top of stack
|
||||
}
|
||||
|
||||
// Copy the standard GDT layout
|
||||
cpu.cpuGdt = {
|
||||
cpu.arch.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
|
||||
@@ -79,27 +83,27 @@ namespace Smp {
|
||||
};
|
||||
|
||||
// Encode the 16-byte TSS descriptor
|
||||
uint64_t base = (uint64_t)&cpu.cpuTss;
|
||||
uint64_t base = (uint64_t)&cpu.arch.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;
|
||||
cpu.arch.cpuGdt.TSS.LimitLow = limit & 0xFFFF;
|
||||
cpu.arch.cpuGdt.TSS.BaseLow = base & 0xFFFF;
|
||||
cpu.arch.cpuGdt.TSS.BaseMiddle = (base >> 16) & 0xFF;
|
||||
cpu.arch.cpuGdt.TSS.AccessByte = 0x89; // Present, 64-bit TSS Available
|
||||
cpu.arch.cpuGdt.TSS.GranularityByte = (limit >> 16) & 0x0F;
|
||||
cpu.arch.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.arch.cpuGdt.TSSHigh.LimitLow = baseUpper & 0xFFFF;
|
||||
cpu.arch.cpuGdt.TSSHigh.BaseLow = (baseUpper >> 16) & 0xFFFF;
|
||||
cpu.arch.cpuGdt.TSSHigh.BaseMiddle = 0;
|
||||
cpu.arch.cpuGdt.TSSHigh.AccessByte = 0;
|
||||
cpu.arch.cpuGdt.TSSHigh.GranularityByte = 0;
|
||||
cpu.arch.cpuGdt.TSSHigh.BaseHigh = 0;
|
||||
|
||||
cpu.tss = &cpu.cpuTss;
|
||||
cpu.arch.tss = &cpu.arch.cpuTss;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -126,13 +130,13 @@ namespace Smp {
|
||||
CpuData& bsp = g_cpus[0];
|
||||
bsp.selfPtr = (uint64_t)&bsp;
|
||||
bsp.cpuIndex = 0;
|
||||
bsp.lapicId = Hal::LocalApic::GetId();
|
||||
bsp.arch.lapicId = Hal::LocalApic::GetId();
|
||||
bsp.currentSlot = -1;
|
||||
bsp.started = true;
|
||||
bsp.hasMwait = Hal::HasMwait();
|
||||
bsp.arch.hasMwait = Hal::HasMwait();
|
||||
|
||||
// BSP uses the global TSS (already set up in PrepareGDT)
|
||||
bsp.tss = &Hal::g_tss;
|
||||
bsp.arch.tss = &Hal::g_tss;
|
||||
|
||||
// Allocate IST1 stack for the BSP (for Double Fault / NMI)
|
||||
void* bspIstPage = Memory::g_pfa->AllocateZeroed();
|
||||
@@ -145,7 +149,7 @@ namespace Smp {
|
||||
|
||||
g_cpuCount = 1;
|
||||
|
||||
KernelLogStream(OK, "SMP") << "BSP initialized (LAPIC ID " << (uint64_t)bsp.lapicId << ")";
|
||||
KernelLogStream(OK, "SMP") << "BSP initialized (LAPIC ID " << (uint64_t)bsp.arch.lapicId << ")";
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
@@ -163,7 +167,7 @@ namespace Smp {
|
||||
// --- Load per-CPU GDT ---
|
||||
Hal::GDTPointer gdtPtr {
|
||||
.Size = sizeof(Hal::BasicGDT) - 1,
|
||||
.GDTAddress = (uint64_t)&cpu->cpuGdt,
|
||||
.GDTAddress = (uint64_t)&cpu->arch.cpuGdt,
|
||||
};
|
||||
LoadGDT(&gdtPtr);
|
||||
ReloadSegments();
|
||||
@@ -175,7 +179,7 @@ namespace Smp {
|
||||
Hal::IDTReload();
|
||||
|
||||
// --- Switch to kernel page tables ---
|
||||
Memory::VMM::LoadCR3(Memory::VMM::g_paging->PML4);
|
||||
Memory::VMM::ActivateKernelAddressSpace();
|
||||
|
||||
// --- Enable SSE ---
|
||||
Hal::EnableSSE();
|
||||
@@ -187,45 +191,38 @@ namespace Smp {
|
||||
Hal::LocalApic::InitializeAP();
|
||||
|
||||
// --- Program SYSCALL MSRs ---
|
||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
||||
efer |= 1 | (1ULL << 11); // SCE | NXE
|
||||
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);
|
||||
Arch::InitializeSyscallEntry();
|
||||
|
||||
// --- Program PAT (entry 1 = WC) ---
|
||||
Hal::InitializePAT();
|
||||
|
||||
// --- Calibrate and start APIC timer ---
|
||||
Timekeeping::ApicTimerInitializeAP();
|
||||
Timekeeping::TimerInitializeAP();
|
||||
|
||||
// --- Check MWAIT support ---
|
||||
cpu->hasMwait = Hal::HasMwait();
|
||||
cpu->arch.hasMwait = Hal::HasMwait();
|
||||
|
||||
// --- Per-CPU power setup (C1E promotion, HWP request) ---
|
||||
Hal::CpuPower::InitializeAp();
|
||||
Arch::CpuPower::InitializeAp();
|
||||
|
||||
// --- Signal that we are online ---
|
||||
cpu->started = true;
|
||||
|
||||
// --- Enable interrupts and enter idle loop ---
|
||||
asm volatile("sti");
|
||||
Arch::EnableInterrupts();
|
||||
|
||||
static volatile uint64_t s_idleMonitor = 0;
|
||||
for (;;) {
|
||||
// Pick up thermal-governor frequency changes decided by the BSP.
|
||||
Hal::CpuPower::ApplyPolicyIfChanged();
|
||||
Arch::CpuPower::ApplyPolicyIfChanged();
|
||||
// Runnable work sends this AP a reschedule IPI. Keep its periodic
|
||||
// scheduler tick masked for the entire idle-context pass so long
|
||||
// firmware waits do not keep generating useless timer interrupts.
|
||||
Timekeeping::ApicTimerEnterApIdle();
|
||||
Timekeeping::TimerEnterApIdle();
|
||||
// Any idle core may run bounded USB/NIC bottom halves. Preserve
|
||||
// the AP's ACPI/MWAIT idle selection after servicing them.
|
||||
Timekeeping::ServiceDeferredWork();
|
||||
Hal::CpuIdle::Wait(10, cpu->hasMwait, &s_idleMonitor);
|
||||
Hal::CpuIdle::Wait(10, cpu->arch.hasMwait, &s_idleMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +239,7 @@ namespace Smp {
|
||||
uint64_t cpuCount = smp.cpuCount;
|
||||
|
||||
KernelLogStream(INFO, "SMP") << "Bootloader reports " << cpuCount << " CPU(s), BSP LAPIC ID "
|
||||
<< (uint64_t)smp.bspLapicId;
|
||||
<< smp.bspHardwareId;
|
||||
|
||||
if (cpuCount <= 1) {
|
||||
KernelLogStream(INFO, "SMP") << "Single CPU system - no APs to boot";
|
||||
@@ -262,13 +259,13 @@ namespace Smp {
|
||||
for (uint64_t i = 0; i < cpuCount; i++) {
|
||||
montauk::boot::BootCpu& info = smp.cpus[i];
|
||||
|
||||
if (info.lapicId == smp.bspLapicId) continue;
|
||||
if (info.hardwareId == smp.bspHardwareId) continue;
|
||||
if (apIndex >= MaxCPUs) break;
|
||||
|
||||
CpuData& ap = g_cpus[apIndex];
|
||||
ap.selfPtr = (uint64_t)≈
|
||||
ap.cpuIndex = apIndex;
|
||||
ap.lapicId = info.lapicId;
|
||||
ap.arch.lapicId = (uint32_t)info.hardwareId;
|
||||
ap.currentSlot = -1;
|
||||
ap.started = false;
|
||||
|
||||
@@ -290,13 +287,13 @@ namespace Smp {
|
||||
volatile bool* flag = &g_cpus[i].started;
|
||||
uint64_t timeout = 100000000;
|
||||
while (!*flag && timeout > 0) {
|
||||
asm volatile("pause");
|
||||
Arch::Pause();
|
||||
timeout--;
|
||||
}
|
||||
|
||||
if (!*flag) {
|
||||
KernelLogStream(ERROR, "SMP") << "AP " << i << " (LAPIC "
|
||||
<< (uint64_t)g_cpus[i].lapicId << ") failed to start";
|
||||
<< (uint64_t)g_cpus[i].arch.lapicId << ") failed to start";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Syscall.cpp
|
||||
* x86_64 SYSCALL/SYSRET setup (implements Arch/Syscall.hpp)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <Arch/Syscall.hpp>
|
||||
#include <Arch/x86_64/MSR.hpp>
|
||||
|
||||
// SyscallEntry.asm
|
||||
extern "C" void SyscallEntry();
|
||||
|
||||
namespace Arch {
|
||||
|
||||
void InitializeSyscallEntry() {
|
||||
// Enable SYSCALL/SYSRET and no-execute page permissions in EFER.
|
||||
uint64_t efer = Hal::ReadMSR(Hal::IA32_EFER);
|
||||
efer |= 1 | (1ULL << 11); // SCE | NXE
|
||||
Hal::WriteMSR(Hal::IA32_EFER, efer);
|
||||
|
||||
// STAR: kernel CS in [47:32], sysret base in [63:48]
|
||||
// SYSCALL: CS=0x08, SS=0x10
|
||||
// SYSRET: CS=0x10+16=0x20|RPL3=0x23, SS=0x10+8=0x18|RPL3=0x1B
|
||||
uint64_t star = (0x0010ULL << 48) | (0x0008ULL << 32);
|
||||
Hal::WriteMSR(Hal::IA32_STAR, star);
|
||||
|
||||
// LSTAR: SYSCALL entry point
|
||||
Hal::WriteMSR(Hal::IA32_LSTAR, (uint64_t)SyscallEntry);
|
||||
|
||||
// FMASK: mask IF on SYSCALL entry (bit 9 = IF)
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
}
|
||||
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SyscallFrame.hpp
|
||||
* x86_64 system-call frame (see Arch/Syscall.hpp)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Arch {
|
||||
|
||||
// Stack frame pushed by SyscallEntry.asm
|
||||
struct SyscallFrame {
|
||||
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
|
||||
uint64_t arg6, arg5, arg4, arg3, arg2, arg1;
|
||||
uint64_t syscall_nr;
|
||||
uint64_t user_rflags, user_rip, user_rsp;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -187,17 +187,18 @@ namespace montauk::boot {
|
||||
// to bring up, then calls SmpInfo::startCpu. The bootloader resumes
|
||||
// the parked AP such that it begins executing `entry(cpu)` on a stack
|
||||
// the bootloader provides, with the same handoff machine state the BSP
|
||||
// received (long mode, paging on, kernel mapped). `entry` recovers its
|
||||
// received (64-bit mode, paging on, kernel mapped). `entry` recovers its
|
||||
// per-CPU context from `cpu->extraArgument`.
|
||||
struct BootCpu;
|
||||
|
||||
struct SmpInfo {
|
||||
uint64_t cpuCount; // total CPUs reported (including the BSP)
|
||||
uint32_t bspLapicId; // local-APIC ID of the bootstrap processor
|
||||
uint64_t bspHardwareId; // hardware ID of the bootstrap processor
|
||||
|
||||
BootCpu* cpus; // HHDM/direct-mapped virtual; `cpuCount` entries.
|
||||
// The entry whose lapicId == bspLapicId is the BSP
|
||||
// and must NOT be started (it is already running).
|
||||
// The entry whose hardwareId == bspHardwareId is
|
||||
// the BSP and must NOT be started (it is already
|
||||
// running).
|
||||
|
||||
// Wake `cpu`, causing it to begin executing `entry(cpu)`. Returns
|
||||
// true if the start request was accepted. `entry` runs on a
|
||||
@@ -207,7 +208,8 @@ namespace montauk::boot {
|
||||
};
|
||||
|
||||
struct BootCpu {
|
||||
uint32_t lapicId; // local-APIC ID of this CPU
|
||||
uint64_t hardwareId; // architectural CPU ID: x86 local-APIC ID,
|
||||
// ARM MPIDR
|
||||
uint64_t extraArgument; // free for the kernel: stash a per-CPU
|
||||
// pointer here before calling startCpu; it is
|
||||
// handed back to `entry` via `cpu->extraArgument`.
|
||||
|
||||
+17
-67
@@ -10,40 +10,31 @@
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <Boot/Boot.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Efi/UEFI.hpp>
|
||||
#include <Common/Panic.hpp>
|
||||
#include <Memory/Memmap.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Platform/Util.hpp>
|
||||
#include <Hal/IDT.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <ACPI/ACPI.hpp>
|
||||
#include <ACPI/CpuIdle.hpp>
|
||||
#include <ACPI/AcpiShutdown.hpp>
|
||||
#include <ACPI/AcpiEvents.hpp>
|
||||
#include <ACPI/EmbeddedController.hpp>
|
||||
#include <ACPI/Battery.hpp>
|
||||
#include <ACPI/AML/AmlParser.hpp>
|
||||
#include <Hal/Apic/ApicInit.hpp>
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <Drivers/PS2/PS2Controller.hpp>
|
||||
#include <Drivers/PS2/Keyboard.hpp>
|
||||
#include <Drivers/PS2/Mouse.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
#include <Drivers/Init.hpp>
|
||||
#include <Graphics/Framebuffer.hpp>
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Hal/CpuPower.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <Arch/Platform.hpp>
|
||||
#include <Fs/Boot.hpp>
|
||||
#include <Sched/Scheduler.hpp>
|
||||
#include <Ipc/Ipc.hpp>
|
||||
#include <Api/Syscall.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
#include <Arch/Smp.hpp>
|
||||
using namespace Kt;
|
||||
|
||||
namespace Memory {
|
||||
@@ -69,7 +60,7 @@ extern "C" void kmain() {
|
||||
}
|
||||
|
||||
if (!montauk::boot::Initialize()) {
|
||||
Hal::Halt();
|
||||
Arch::HaltForever();
|
||||
}
|
||||
const montauk::boot::BootInfo& boot = montauk::boot::Info();
|
||||
const montauk::boot::Framebuffer& framebuffer = boot.framebuffer;
|
||||
@@ -88,12 +79,7 @@ extern "C" void kmain() {
|
||||
);
|
||||
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Hal::PrepareGDT();
|
||||
Hal::BridgeLoadGDT();
|
||||
|
||||
Hal::EnableSSE();
|
||||
#endif
|
||||
Arch::EarlyInitialize();
|
||||
|
||||
Memory::HHDMBase = boot.hhdmBase;
|
||||
|
||||
@@ -112,26 +98,19 @@ extern "C" void kmain() {
|
||||
heap.Walk();
|
||||
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Hal::IDTInitialize();
|
||||
Arch::InitializeTraps();
|
||||
|
||||
Memory::VMM::Paging g_paging{};
|
||||
Memory::VMM::g_paging = &g_paging;
|
||||
g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), boot.memoryMap, framebuffer);
|
||||
|
||||
Hal::InitializePAT();
|
||||
|
||||
#endif
|
||||
Arch::InitializeMemoryAttributes();
|
||||
|
||||
Graphics::Framebuffer::Initialize(framebuffer);
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Graphics::Framebuffer::MapWriteCombining();
|
||||
#endif
|
||||
|
||||
Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(boot.rsdpPhysical));
|
||||
|
||||
#if defined (__x86_64__)
|
||||
if (g_acpi.GetXSDT() != nullptr) {
|
||||
Hal::AcpiShutdown::Initialize(g_acpi.GetXSDT());
|
||||
|
||||
@@ -140,15 +119,11 @@ extern "C" void kmain() {
|
||||
Hal::EmbeddedController::Initialize(g_acpi.GetXSDT());
|
||||
Hal::Battery::Initialize();
|
||||
|
||||
Hal::CpuIdle::Initialize(g_acpi.GetXSDT());
|
||||
// Interrupt controllers, this CPU's per-CPU data, idle and power
|
||||
// management.
|
||||
Arch::InitializeInterrupts(g_acpi.GetXSDT());
|
||||
|
||||
Hal::ApicInitialize(g_acpi.GetXSDT());
|
||||
|
||||
Smp::InitBsp();
|
||||
|
||||
Hal::CpuPower::InitializeBsp();
|
||||
|
||||
asm volatile("sti");
|
||||
Arch::EnableInterrupts();
|
||||
|
||||
Hal::AcpiEvents::Initialize(g_acpi.GetXSDT());
|
||||
|
||||
@@ -163,11 +138,9 @@ extern "C" void kmain() {
|
||||
Drivers::ProbeEarly();
|
||||
Drivers::InitializeGraphics();
|
||||
|
||||
Timekeeping::ApicTimerInitialize();
|
||||
Timekeeping::TimerInitialize();
|
||||
|
||||
Drivers::PS2::Initialize();
|
||||
Drivers::PS2::Keyboard::Initialize();
|
||||
Drivers::PS2::Mouse::Initialize();
|
||||
Arch::InitializePlatformDevices();
|
||||
|
||||
// Hybrid graphics: switch off a discrete GPU nothing drives before
|
||||
// the normal probe can claim its USB-C/audio functions.
|
||||
@@ -178,7 +151,6 @@ extern "C" void kmain() {
|
||||
Drivers::InitializeStorage();
|
||||
Drivers::InitializeAudio();
|
||||
}
|
||||
#endif
|
||||
|
||||
// UEFI runtime services are optional (absent on legacy-BIOS boots).
|
||||
if (boot.has(montauk::boot::FeatureEfiSystemTable)) {
|
||||
@@ -188,9 +160,7 @@ extern "C" void kmain() {
|
||||
|
||||
Fs::InitializeBootFilesystems(boot.modules);
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Hal::LoadTSS();
|
||||
#endif
|
||||
Arch::PrepareUserMode();
|
||||
|
||||
montauk::abi::InitializeSyscalls();
|
||||
|
||||
@@ -199,36 +169,16 @@ extern "C" void kmain() {
|
||||
Fs::LogProtectedPaths();
|
||||
Ipc::Initialize();
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Smp::BootAPs(boot.smp);
|
||||
#endif
|
||||
|
||||
#if defined (__x86_64__)
|
||||
Drivers::PS2::Mouse::FlushState();
|
||||
#endif
|
||||
Arch::LateInitialize();
|
||||
|
||||
Kt::SuppressKernelLog();
|
||||
Sched::Spawn("0:/os/init.elf");
|
||||
|
||||
Timekeeping::EnableSchedulerTick();
|
||||
|
||||
#if defined (__x86_64__)
|
||||
// Use MWAIT for deeper C-states if available, otherwise HLT.
|
||||
auto* bspCpu = Smp::GetCpuData(0);
|
||||
|
||||
if (bspCpu && bspCpu->hasMwait) {
|
||||
static volatile uint64_t s_bspIdleMonitor = 0;
|
||||
for (;;) {
|
||||
Timekeeping::IdleOnce(true, &s_bspIdleMonitor);
|
||||
}
|
||||
} else {
|
||||
for (;;) {
|
||||
Timekeeping::IdleOnce(false);
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (;;) {
|
||||
Timekeeping::IdleOnce(false);
|
||||
Timekeeping::IdleOnce();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -280,13 +280,21 @@ namespace montauk::boot {
|
||||
if (count > kMaxCpus) count = kMaxCpus;
|
||||
for (uint64_t i = 0; i < count; i++) {
|
||||
limine_mp_info* info = resp->cpus[i];
|
||||
g_cpus[i].lapicId = info->lapic_id;
|
||||
#if defined(__x86_64__)
|
||||
g_cpus[i].hardwareId = info->lapic_id;
|
||||
#elif defined(__aarch64__)
|
||||
g_cpus[i].hardwareId = info->mpidr;
|
||||
#endif
|
||||
g_cpus[i].extraArgument = 0;
|
||||
g_cpus[i].entry = nullptr;
|
||||
g_cpus[i].native = info;
|
||||
}
|
||||
out.smp.cpuCount = count;
|
||||
out.smp.bspLapicId = resp->bsp_lapic_id;
|
||||
#if defined(__x86_64__)
|
||||
out.smp.bspHardwareId = resp->bsp_lapic_id;
|
||||
#elif defined(__aarch64__)
|
||||
out.smp.bspHardwareId = resp->bsp_mpidr;
|
||||
#endif
|
||||
out.smp.cpus = g_cpus;
|
||||
out.smp.startCpu = &LimineStartCpu;
|
||||
out.features |= FeatureSmp;
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
*/
|
||||
|
||||
#include "Panic.hpp"
|
||||
#include "../CppLib/BoxUI.hpp"
|
||||
#include "../Drivers/Graphics/IntelGPU.hpp"
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <CppLib/BoxUI.hpp>
|
||||
#include <Drivers/Graphics/IntelGPU.hpp>
|
||||
|
||||
static constexpr int BoxWidth = 72;
|
||||
static constexpr int BoxWidth = PanicBoxWidth;
|
||||
|
||||
static void PrintHorizontalEdge(const char* left, const char* right) {
|
||||
kerr << left;
|
||||
@@ -15,68 +16,7 @@ static void PrintHorizontalEdge(const char* left, const char* right) {
|
||||
kerr << right << "\n";
|
||||
}
|
||||
|
||||
static void PrintPageFaultInfo(System::PanicFrame*& frame) {
|
||||
auto* pf = (System::PageFaultPanicFrame*)frame;
|
||||
frame = (System::PanicFrame*)&pf->IP;
|
||||
|
||||
uint64_t cr2;
|
||||
asm volatile("mov %%cr2, %0" : "=r"(cr2));
|
||||
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, BoxWidth);
|
||||
|
||||
PrintBoxedLine(kerr, "Page Fault Error:", BoxWidth, true);
|
||||
PrintBoxedDec(kerr, "Present", pf->PageFaultError.Present, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Write", pf->PageFaultError.Write, BoxWidth);
|
||||
PrintBoxedDec(kerr, "User", pf->PageFaultError.User, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Reserved Write", pf->PageFaultError.ReservedWrite, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Instruction Fetch",pf->PageFaultError.InstructionFetch, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Protection Key", pf->PageFaultError.ProtectionKey, BoxWidth);
|
||||
PrintBoxedDec(kerr, "Shadow Stack", pf->PageFaultError.ShadowStack, BoxWidth);
|
||||
PrintBoxedDec(kerr, "SGX", pf->PageFaultError.SGX, BoxWidth);
|
||||
}
|
||||
|
||||
static void PrintGPFInfo(System::PanicFrame*& frame) {
|
||||
auto* gpf = (System::GPFPanicFrame*)frame;
|
||||
frame = (System::PanicFrame*)&gpf->IP;
|
||||
|
||||
PrintBoxedLine(kerr, "General Protection Fault:", BoxWidth, true);
|
||||
PrintBoxedDec(kerr, "Error Code", gpf->GeneralProtectionFaultError, BoxWidth);
|
||||
}
|
||||
|
||||
// Link-time base of the kernel image (see linker-x86_64.ld).
|
||||
static constexpr uint64_t KernelBaseVirt = 0xFFFFFFFF80000000ULL;
|
||||
|
||||
static void PrintRegisters(System::PanicFrame* frame) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "Registers:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Instruction Pointer", frame->IP, BoxWidth);
|
||||
|
||||
// The absolute address means nothing without the link map. Its offset from
|
||||
// the kernel's load base is what `nm bin-x86_64/kernel` can be searched
|
||||
// for directly, so print that next to it.
|
||||
if (frame->IP >= KernelBaseVirt)
|
||||
PrintBoxedHex(kerr, " kernel+", frame->IP - KernelBaseVirt, BoxWidth);
|
||||
|
||||
|
||||
PrintBoxedHex(kerr, "Code Segment", frame->CS, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Flags", frame->Flags, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Pointer", frame->SP, BoxWidth);
|
||||
PrintBoxedHex(kerr, "Stack Segment", frame->SS, BoxWidth);
|
||||
}
|
||||
|
||||
[[noreturn]] static void Halt() {
|
||||
while (true) {
|
||||
#if defined (__x86_64__)
|
||||
asm ("cli");
|
||||
asm ("hlt");
|
||||
#elif defined (__aarch64__) || defined (__riscv)
|
||||
asm ("wfi");
|
||||
#elif defined (__loongarch64)
|
||||
asm ("idle 0");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Panic(const char *meditationString, System::PanicFrame* frame) {
|
||||
void Panic(const char *meditationString, Arch::PanicFrame* frame) {
|
||||
// Bring scanout back to buffer 0 first: if a page-flipped client was
|
||||
// showing the other buffer, the panic box would land on an invisible
|
||||
// surface. Safe no-op when the GPU driver is inactive.
|
||||
@@ -96,32 +36,19 @@ void Panic(const char *meditationString, System::PanicFrame* frame) {
|
||||
PrintBoxedLine(kerr, meditationString, BoxWidth);
|
||||
PrintBoxedLine(kerr, "", BoxWidth);
|
||||
|
||||
// CPU state (x86_64 only)
|
||||
#if defined (__x86_64__)
|
||||
// CPU state at the faulting exception
|
||||
if (frame != nullptr) {
|
||||
PrintBoxedSeparator(kerr, BoxWidth);
|
||||
PrintBoxedLine(kerr, "CPU State:", BoxWidth, true);
|
||||
PrintBoxedHex(kerr, "Interrupt Vector", frame->InterruptVector, BoxWidth);
|
||||
|
||||
if (frame->InterruptVector == 0xE)
|
||||
PrintPageFaultInfo(frame);
|
||||
else if (frame->InterruptVector == 0xD)
|
||||
PrintGPFInfo(frame);
|
||||
|
||||
PrintRegisters(frame);
|
||||
Arch::PrintPanicFrame(frame);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Footer
|
||||
PrintBoxedLine(kerr, "", BoxWidth);
|
||||
PrintHorizontalEdge(BOXUI_BL, BOXUI_BR);
|
||||
kerr << BOXUI_ANSI_RESET;
|
||||
|
||||
#if defined (__x86_64__)
|
||||
// The framebuffer is write-combining. Drain the final panic text before
|
||||
// halting all forward progress so it cannot remain in a CPU write buffer.
|
||||
asm volatile("sfence" ::: "memory");
|
||||
#endif
|
||||
Arch::StoreFence();
|
||||
|
||||
Halt();
|
||||
Arch::HaltForever();
|
||||
}
|
||||
@@ -1,5 +1,18 @@
|
||||
#pragma once
|
||||
#include <Platform/Registers.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
void Panic(const char *meditationString, System::PanicFrame* frame);
|
||||
namespace Arch {
|
||||
// The CPU state an exception handler captured; its layout belongs to
|
||||
// the architecture (Arch/<arch>/), generic code only passes it along.
|
||||
struct PanicFrame;
|
||||
|
||||
// Print the frame's CPU state into the panic box. Implemented by the
|
||||
// architecture.
|
||||
void PrintPanicFrame(PanicFrame* frame);
|
||||
}
|
||||
|
||||
// Width of the panic screen's box, shared with Arch::PrintPanicFrame.
|
||||
static constexpr int PanicBoxWidth = 72;
|
||||
|
||||
// Print the panic screen and halt this CPU. `frame` may be nullptr.
|
||||
[[noreturn]] void Panic(const char *meditationString, Arch::PanicFrame* frame);
|
||||
@@ -21,17 +21,3 @@ enum TerminationCodes {
|
||||
TerminatedKilled = TerminationBase + 9, // SIGKILL
|
||||
TerminatedAccessViolation = TerminationBase + 11 // SIGSEGV
|
||||
};
|
||||
|
||||
// Termination code for a user-mode CPU exception. The exact vector is
|
||||
// kept in the crash report; this is only the category.
|
||||
inline TerminationCodes TerminationFromVector(uint8_t vector) {
|
||||
switch (vector) {
|
||||
case 0x06: return TerminatedIllegalInstruction; // #UD
|
||||
case 0x01: // #DB
|
||||
case 0x03: return TerminatedBreakpoint; // #BP
|
||||
case 0x00: // #DE
|
||||
case 0x10: // #MF
|
||||
case 0x13: return TerminatedArithmeticError; // #XM
|
||||
default: return TerminatedAccessViolation; // #PF, #GP, #SS, #NP, #AC, #OF, #BR, #CP, ...
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Itanium.cpp
|
||||
* ABI.cpp
|
||||
* Definitions for the Itanium C++ ABI
|
||||
* Copyright (c) Daniel Hammer, Limine Contributors (via Limine C++ example)
|
||||
*/
|
||||
|
||||
#include <Platform/Util.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
|
||||
// The following stubs are required by the Itanium C++ ABI (the one we use,
|
||||
// regardless of the "Itanium" nomenclature).
|
||||
@@ -12,6 +12,6 @@
|
||||
// but should not be removed, unless you know what you are doing.
|
||||
extern "C" {
|
||||
int __cxa_atexit(void (*)(void *), void *, void *) { return 0; }
|
||||
void __cxa_pure_virtual() { Hal::Halt(); }
|
||||
void __cxa_pure_virtual() { Arch::HaltForever(); }
|
||||
void *__dso_handle;
|
||||
}
|
||||
Loaded 100 of 164 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user