# 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//`, 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 `#if`s outside `src/Arch/` (the Limine adapter is the one exception: `limine.h` has per-architecture SMP structures); - no generic file includes a port's private headers (``); - with clang installed, the kernel compiles for `aarch64` and 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//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: 1. `Arch::EarlyInitialize()` - descriptor tables, FPU/SIMD enable. No allocator. 2. Frame allocator and heap come up (generic). 3. `Arch::InitializeTraps()` - exception vectors. 4. `Paging::Init()` switches to the kernel's own page tables, then `Arch::InitializeMemoryAttributes()` (x86: PAT for write-combining). 5. ACPI tables are parsed (generic). `Arch::InitializeInterrupts(xsdt)` brings up interrupt controllers, calls `Smp::InitBsp()`, idle and power management; kmain then calls `Arch::EnableInterrupts()`. 6. PCI, AML, drivers (generic); `Timekeeping::TimerInitialize()` registers `IRQ_TIMER` and calls `Arch::Timer::InitializeBsp(1)`. 7. `Arch::InitializePlatformDevices()` (x86: PS/2). 8. `Arch::PrepareUserMode()`, `montauk::abi::InitializeSyscalls()` (calls `Arch::InitializeSyscallEntry()`), scheduler, IPC. 9. `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 (`ComposeMsi` returns false, so drivers fall back to INTx/polling). - Generic timer tick (`Arch::Timer::InitializeBsp` returns 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 1. Create `kernel/src/Arch//` with the six inline headers listed above and stubs for the out-of-line functions (copy `aarch64/` as a template). 2. Add the target to `Arch/Arch.hpp` and to the `#if` chains at the end of the dispatching interface headers. 3. Add `KernelStartSymbol` / `KernelEndSymbol` to `kernel/linker-.ld`, and the `ld.lld` emulation to `LD_EMULATION_` in `kernel/GNUmakefile`. 4. Teach `Boot/Protocols/LimineProtocol.cpp` the architecture's MP fields. 5. Run `./scripts/check-arch-neutral.sh ` until it passes.