5.8 KiB
Porting the Kernel to a New Architecture
The kernel is written so that everything outside kernel/src/Arch/ is
architecture-neutral. A port is one directory, kernel/src/Arch/<arch>/,
implementing the interfaces declared in kernel/src/Arch/*.hpp (plus a few
interface headers elsewhere, listed below). The build compiles the generic
tree plus Arch/$(ARCH)/ only.
scripts/check-arch-neutral.sh enforces the boundary and should pass before
every commit that touches the kernel:
- no inline assembly, assembly sources or architecture
#ifs outsidesrc/Arch/(the Limine adapter is the one exception:limine.hhas per-architecture SMP structures); - no generic file includes a port's private headers (
<Arch/x86_64/...>); - with clang installed, the kernel compiles for
aarch64and every symbol the generic code references is defined by the port.
Layout
kernel/src/Arch/
Arch.hpp target selection (MONTAUK_ARCH_*), MONTAUK_ARCH_NAME
Cpu.hpp inline CPU primitives (interrupt state, halt, barriers,
timestamp counter, TLB, page-table root)
Interrupts.hpp IRQ slots, handler registration, legacy line routing,
MSI message composition, IPIs
Smp.hpp Smp::CpuData (generic fields + arch sub-structs),
per-CPU base access, InitBsp / BootAPs
Context.hpp thread context: FPU area, initial kernel stack,
SwitchContext, ActivateThread, EnterUserMode
Abi.hpp user ABI: ELF machine, relocation kinds, TLS variant,
exit stub, initial user stack, EFI calling convention
Syscall.hpp SyscallFrame contract and entry-point installation
Timer.hpp per-CPU tick timer hardware
Platform.hpp boot-sequence hooks, platform device list, reboot
CpuPower.hpp frequency scaling / thermal governor (may be no-ops)
x86_64/ the complete port
aarch64/ skeleton port
Other interfaces a port implements:
| Header | What the port provides |
|---|---|
Memory/Paging.hpp |
the whole Memory::VMM::Paging class: page-table format, kernel and user mappings, address-space creation and teardown |
Io/IoPort.hpp |
port I/O (Io::In8...), or the absent-device fallback with HasPortIo = false |
ACPI/AcpiSleep.hpp |
Initialize / IsS3Available / Suspend (S3 via the FACS waking vector is an x86 mechanism) |
Common/Panic.hpp |
Arch::PanicFrame and Arch::PrintPanicFrame |
Interface headers that need inline definitions include the port's header of
the same name at their end (Arch/Cpu.hpp -> Arch/<arch>/Cpu.hpp); those
are Cpu.hpp, IoPort.hpp, PerCpu.hpp, Context.hpp, Abi.hpp and
SyscallFrame.hpp. Everything else is an ordinary out-of-line function.
Boot sequence
kmain (Boot/Main.cpp) owns the order and calls the port at fixed points:
Arch::EarlyInitialize()- descriptor tables, FPU/SIMD enable. No allocator.- Frame allocator and heap come up (generic).
Arch::InitializeTraps()- exception vectors.Paging::Init()switches to the kernel's own page tables, thenArch::InitializeMemoryAttributes()(x86: PAT for write-combining).- ACPI tables are parsed (generic).
Arch::InitializeInterrupts(xsdt)brings up interrupt controllers, callsSmp::InitBsp(), idle and power management; kmain then callsArch::EnableInterrupts(). - PCI, AML, drivers (generic);
Timekeeping::TimerInitialize()registersIRQ_TIMERand callsArch::Timer::InitializeBsp(1). Arch::InitializePlatformDevices()(x86: PS/2).Arch::PrepareUserMode(),montauk::abi::InitializeSyscalls()(callsArch::InitializeSyscallEntry()), scheduler, IPC.Smp::BootAPs(),Arch::LateInitialize(), first process, idle loop.
Assumptions the generic kernel makes
- 64-bit, little-endian, 4 KiB pages.
- A higher-half direct map of physical memory (
Memory::HHDM), as Limine provides on every architecture. - User space in the lower half of each address space, kernel in the upper half; an address space is identified by the physical address of its root table (on ARM, the TTBR0 table; the kernel half lives in TTBR1).
- Firmware tables via ACPI (
Hal::ACPI); device-tree-only platforms would need a discovery path first. - Interrupts are numbered IRQ slots (
Arch::IRQ_COUNT); slot numbers for MSI-capable drivers are fixed in the drivers.
The aarch64 skeleton
Real: CPU primitives (DAIF, WFI, CNTVCT_EL0, TLBI, TTBR0), per-CPU data via
TPIDR_EL1, the context switch and EL0 entry (Context.S), TLS through
TPIDR_EL0, the ELF/TLS/exit-stub ABI (syscall number in x8), the IRQ slot
dispatch table (ArchIrqDispatch), panic-frame printing.
Not yet written (each is marked TODO(aarch64)):
Paging.cpp: translation tables and MAIR (every method panics).- Exception vectors (
VBAR_EL1): synchronous faults,svc, IRQs. - GIC driver: SPI routing, SGIs for IPIs, MSIs via ITS/GICv2m
(
ComposeMsireturns false, so drivers fall back to INTx/polling). - Generic timer tick (
Arch::Timer::InitializeBspreturns false). - Secondary CPU start-up, PSCI reset and suspend.
Building it needs clang and ld.lld (make kernel ARCH=aarch64;
KERNEL_LD= points at a different ld.lld, e.g. rustup's
lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld/ld.lld).
Adding another architecture
- Create
kernel/src/Arch/<arch>/with the six inline headers listed above and stubs for the out-of-line functions (copyaarch64/as a template). - Add the target to
Arch/Arch.hppand to the#ifchains at the end of the dispatching interface headers. - Add
KernelStartSymbol/KernelEndSymboltokernel/linker-<arch>.ld, and theld.lldemulation toLD_EMULATION_<arch>inkernel/GNUmakefile. - Teach
Boot/Protocols/LimineProtocol.cppthe architecture's MP fields. - Run
./scripts/check-arch-neutral.sh <arch>until it passes.