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
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user