141 lines
4.9 KiB
C++
141 lines
4.9 KiB
C++
/*
|
|
* 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 <Arch/aarch64/Gic.hpp>
|
|
#include <Arch/aarch64/Bringup.hpp>
|
|
#include <ACPI/FADT.hpp>
|
|
#include <Terminal/Terminal.hpp>
|
|
#include <CppLib/Stream.hpp>
|
|
|
|
using namespace Kt;
|
|
|
|
extern "C" char ArchExceptionVectors[]; // Vectors.S
|
|
|
|
namespace Arch {
|
|
|
|
// SCTLR_EL1.WXN: writable memory is execute-never
|
|
static constexpr uint64_t SctlrWxn = 1ULL << 19;
|
|
// SCTLR_EL1.UCT/UCI: EL0 may read CTR_EL0 and clean/invalidate caches
|
|
// by address (what __builtin___clear_cache needs for generated code).
|
|
static constexpr uint64_t SctlrUct = 1ULL << 15;
|
|
static constexpr uint64_t SctlrUci = 1ULL << 26;
|
|
|
|
// PSCI 0.2+ function IDs (SMC32 calling convention)
|
|
static constexpr uint64_t PsciSystemOff = 0x84000008;
|
|
static constexpr uint64_t PsciSystemReset = 0x84000009;
|
|
|
|
// How the firmware takes PSCI calls, from the FADT's ARM_BOOT_ARCH.
|
|
enum class PsciConduit { None, Smc, Hvc };
|
|
static PsciConduit g_psci = PsciConduit::None;
|
|
|
|
static uint64_t PsciCall(uint64_t function) {
|
|
register uint64_t x0 asm("x0") = function;
|
|
register uint64_t x1 asm("x1") = 0;
|
|
register uint64_t x2 asm("x2") = 0;
|
|
register uint64_t x3 asm("x3") = 0;
|
|
if (g_psci == PsciConduit::Hvc) {
|
|
asm volatile("hvc #0" : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3) :: "memory");
|
|
} else if (g_psci == PsciConduit::Smc) {
|
|
asm volatile("smc #0" : "+r"(x0), "+r"(x1), "+r"(x2), "+r"(x3) :: "memory");
|
|
}
|
|
return x0;
|
|
}
|
|
|
|
static void InitializePsci(Hal::ACPI::CommonSDTHeader* xsdt) {
|
|
Hal::FADT::ParsedFADT fadt{};
|
|
if (!Hal::FADT::Parse(xsdt, fadt) || !fadt.Valid) return;
|
|
if (!(fadt.ArmBootArchFlags & Hal::FADT::ArmBootPsciCompliant)) {
|
|
KernelLogStream(WARNING, "PSCI") << "Firmware is not PSCI compliant: no reset or power-off";
|
|
return;
|
|
}
|
|
g_psci = (fadt.ArmBootArchFlags & Hal::FADT::ArmBootPsciUseHvc) ? PsciConduit::Hvc : PsciConduit::Smc;
|
|
KernelLogStream(OK, "PSCI") << "PSCI through " << (g_psci == PsciConduit::Hvc ? "HVC" : "SMC");
|
|
}
|
|
|
|
void InitializeCpuRegisters() {
|
|
// Run on SP_EL1 so exceptions taken from the kernel arrive on the
|
|
// stack it is already using (Vectors.S has no SP_EL0 handlers).
|
|
asm volatile("mov x9, sp\n\tmsr spsel, #1\n\tmov sp, x9" ::: "x9", "memory");
|
|
|
|
// The vectors need no allocation, so take over from the
|
|
// bootloader's right away.
|
|
asm volatile("msr vbar_el1, %0\n\tisb" :: "r"(ArchExceptionVectors) : "memory");
|
|
|
|
// Kernel mappings are RWX (Paging.cpp), so writable must not
|
|
// imply execute-never; user code may maintain its own caches.
|
|
uint64_t sctlr;
|
|
asm volatile("mrs %0, sctlr_el1" : "=r"(sctlr));
|
|
sctlr &= ~SctlrWxn;
|
|
sctlr |= SctlrUct | SctlrUci;
|
|
asm volatile("msr sctlr_el1, %0\n\tisb" :: "r"(sctlr));
|
|
|
|
// 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));
|
|
}
|
|
|
|
void EarlyInitialize() {
|
|
// No per-CPU data yet: Smp::TryGetCurrentCpuData must see null.
|
|
asm volatile("msr tpidr_el1, xzr");
|
|
|
|
InitializeCpuRegisters();
|
|
}
|
|
|
|
// Installed by EarlyInitialize.
|
|
void InitializeTraps() {
|
|
KernelLogStream(OK, "Arch") << "Installed exception vectors at 0x" << base::hex
|
|
<< (uint64_t)ArchExceptionVectors;
|
|
}
|
|
|
|
// MAIR_EL1 attribute indices are chosen together with the page-table
|
|
// format (Paging.cpp), so there is nothing to program separately.
|
|
void InitializeMemoryAttributes() {
|
|
}
|
|
|
|
void InitializeInterrupts(Hal::ACPI::CommonSDTHeader* xsdt) {
|
|
Smp::InitBsp();
|
|
if (!Gic::Initialize(xsdt)) {
|
|
KernelLogStream(ERROR, "Arch") << "aarch64: no interrupt controller, running without IRQs";
|
|
}
|
|
InitializePsci(xsdt);
|
|
}
|
|
|
|
void InitializePlatformDevices() {
|
|
}
|
|
|
|
// EL0 entry needs no global state beyond Arch::ActivateThread.
|
|
void PrepareUserMode() {
|
|
}
|
|
|
|
void LateInitialize() {
|
|
}
|
|
|
|
void ListPlatformDevices(PlatformDeviceSink sink, void* context) {
|
|
if (Gic::IsInitialized()) {
|
|
sink(context, 1, "GICv2", "ARM Generic Interrupt Controller");
|
|
sink(context, 2, "Generic Timer", "ARM architected timer (EL1 virtual)");
|
|
}
|
|
}
|
|
|
|
void Reboot() {
|
|
PsciCall(PsciSystemReset);
|
|
HaltForever();
|
|
}
|
|
|
|
void PowerOff() {
|
|
if (g_psci != PsciConduit::None) {
|
|
PsciCall(PsciSystemOff);
|
|
}
|
|
}
|
|
|
|
}
|