feat: make kernel architecture-agnostic; primitive work on arm port

This commit is contained in:
daniel committed 2026-10-24 21:25:18 +02:00
1 parent dab7d5427b
commit 1bae77ec84
164 files changed
+3664 -1641

No files matched your search

+55
View File
@@ -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;
}
}
+26
View File
@@ -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;
}
}
+144
View File
@@ -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
+53
View File
@@ -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);
}
}
+14
View File
@@ -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;
}
+120
View File
@@ -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");
}
}
+26
View File
@@ -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
};
}
+52
View File
@@ -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);
}
}
+28
View File
@@ -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();
}
};
+110
View File
@@ -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");
}
}
+28
View File
@@ -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);
}
}
+34
View File
@@ -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();
}
}
+64
View File
@@ -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();
}
}
+60
View File
@@ -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)";
}
}
+16
View File
@@ -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() {
}
}
+21
View File
@@ -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
};
}
+24
View File
@@ -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() {
}
}