From 772eaee9f41a9a2d2f3b9574397f68bbfbad1a5c Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 20 Jun 2026 21:25:24 +0200 Subject: [PATCH] feat: introduce bootloader-agnostic Boot Contract, add btlist command to list connected Bluetooth devices --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Boot/Boot.cpp | 50 ++++ kernel/src/Boot/Boot.hpp | 31 ++ kernel/src/Boot/BootInfo.hpp | 236 +++++++++++++++ kernel/src/Boot/BootProtocol.hpp | 100 +++++++ kernel/src/Boot/Protocols/LimineProtocol.cpp | 300 +++++++++++++++++++ kernel/src/Efi/UEFI.hpp | 6 +- kernel/src/Fs/Boot.cpp | 22 +- kernel/src/Fs/Boot.hpp | 4 +- kernel/src/Graphics/Cursor.cpp | 10 +- kernel/src/Graphics/Cursor.hpp | 4 +- kernel/src/Hal/SmpBoot.cpp | 44 ++- kernel/src/Hal/SmpBoot.hpp | 7 +- kernel/src/Main.cpp | 61 ++-- kernel/src/Memory/Memmap.cpp | 20 +- kernel/src/Memory/Memmap.hpp | 11 +- kernel/src/Memory/Paging.cpp | 63 +++- kernel/src/Memory/Paging.hpp | 8 +- kernel/src/Platform/Limine.hpp | 102 ------- programs/src/btlist/main.cpp | 36 +++ 20 files changed, 904 insertions(+), 213 deletions(-) create mode 100644 kernel/src/Boot/Boot.cpp create mode 100644 kernel/src/Boot/Boot.hpp create mode 100644 kernel/src/Boot/BootInfo.hpp create mode 100644 kernel/src/Boot/BootProtocol.hpp create mode 100644 kernel/src/Boot/Protocols/LimineProtocol.cpp delete mode 100644 kernel/src/Platform/Limine.hpp create mode 100644 programs/src/btlist/main.cpp diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 65135bd..8812502 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 129 +#define MONTAUK_BUILD_NUMBER 132 diff --git a/kernel/src/Boot/Boot.cpp b/kernel/src/Boot/Boot.cpp new file mode 100644 index 0000000..168192e --- /dev/null +++ b/kernel/src/Boot/Boot.cpp @@ -0,0 +1,50 @@ +/* + * Boot.cpp + * Kernel-side acquisition and validation of the Montauk Boot Contract + * Copyright (c) 2026 Daniel Hammer +*/ + +#include "Boot.hpp" +#include "BootProtocol.hpp" + +namespace montauk::boot { + + // The contract value lives in kernel .bss so it survives the bootloader + // reclaiming its own structures. Note: the *arrays* it points at do not + // (see the lifetime model in BootInfo.hpp) -- consumers must read them + // during early boot. + static BootInfo g_info{}; + + const BootInfo& Info() { + return g_info; + } + + bool Initialize() { + // Translate the active bootloader's native handoff into the contract. + if (!Acquire(g_info)) { + // The environment is unusable, or the protocol is unsupported. + // We cannot log (no console yet) -- the caller halts. + return false; + } + + // The adapter must speak the same contract revision we compiled + // against. A mismatch means struct layouts disagree; continuing + // would read garbage. We cannot trust the framebuffer either, so + // signal the caller to halt. + if (g_info.contractVersion != ContractVersion) { + return false; + } + + // A console-capable framebuffer is required to report anything at + // all, so its absence must also be a silent halt. Every other + // required field (e.g. the memory map) is validated by its consumer + // once the console is up, so those failures are visible. + if (!g_info.has(FeatureFramebuffer) + || g_info.framebuffer.address == 0) { + return false; + } + + return true; + } + +} diff --git a/kernel/src/Boot/Boot.hpp b/kernel/src/Boot/Boot.hpp new file mode 100644 index 0000000..2c4010d --- /dev/null +++ b/kernel/src/Boot/Boot.hpp @@ -0,0 +1,31 @@ +/* + * Boot.hpp + * Kernel-side accessor for the Montauk Boot Contract + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include "BootInfo.hpp" + +namespace montauk::boot { + + // Acquire the boot contract from the active bootloader adapter and + // validate that the always-required fields are present. + // + // Returns false ONLY for failures the kernel cannot even report (the + // adapter rejected the environment, or no usable framebuffer for the + // console exists) -- the caller should Halt() in that case. Missing + // *required* data that is severe but post-console (e.g. no memory map) + // raises a Panic with a descriptive message instead. + // + // Must be called exactly once, very early in kmain(), after global + // constructors but before any consumer of boot data. + bool Initialize(); + + // The validated boot contract. Only valid after Initialize() returns + // true. Returned by const reference: the BootInfo value is immutable, + // but the bootloader-owned arrays it points at (e.g. smp.cpus) remain + // mutable through their pointers, which is what AP startup needs. + const BootInfo& Info(); + +} diff --git a/kernel/src/Boot/BootInfo.hpp b/kernel/src/Boot/BootInfo.hpp new file mode 100644 index 0000000..f7090ad --- /dev/null +++ b/kernel/src/Boot/BootInfo.hpp @@ -0,0 +1,236 @@ +/* + * BootInfo.hpp + * The Montauk Boot Contract (MBC) + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include + +// ==================================================================== +// The Montauk Boot Contract (MBC) +// ==================================================================== +// +// This header is the SOLE interface between the MontaukOS kernel and +// whatever bootloader brought it to life. The kernel does not know, and +// must not care, whether it was loaded by Limine, by a future bespoke +// "Montauk Loader", by a UEFI stub, or by a hypervisor shim. It knows +// only that *something* fulfilled this contract and handed it a fully +// populated `BootInfo`. +// +// A bootloader fulfills the contract in two halves: +// +// 1. The HANDOFF STATE (machine state on entry to `kmain`). This part +// of the contract cannot be expressed in a struct; it is documented +// in detail in BootContract.hpp and must be honoured before the first +// instruction of the kernel runs. +// +// 2. This `BootInfo` STRUCTURE, produced by a per-bootloader adapter +// that implements `montauk::boot::Acquire()` (declared in +// BootProtocol.hpp). Exactly one adapter is linked into the kernel. +// +// DESIGN RULES (so a novel bootloader can genuinely satisfy the contract): +// +// * No type in this file may name, include, or depend on any particular +// bootloader's headers. Everything here is plain Montauk-native POD. +// * Every field has a single, fully-specified meaning (units, physical +// vs. virtual, ownership/lifetime). Adapters translate; they do not +// reinterpret. +// * Optional capabilities are advertised through `features`. A field +// guarded by a feature bit is only meaningful when that bit is set. +// +// MEMORY / LIFETIME MODEL: +// +// The arrays referenced by BootInfo (memory regions, modules, CPUs, the +// EFI memory map) are NOT owned by the kernel. They live in memory the +// bootloader marked "bootloader-reclaimable". The kernel must consume +// everything it needs from them during early boot, BEFORE it reclaims +// that memory. After early boot the pointers in BootInfo must be treated +// as dangling. `BootInfo` itself is a value owned by the kernel (it lives +// in kernel .bss; see Boot.cpp), so it survives reclamation, but the +// things it points at do not. +// +// ADDRESS CONVENTIONS (read carefully -- they are not all the same): +// +// * "physical" -- a raw physical address. The kernel reaches it with +// Memory::HHDM(addr). +// * "HHDM/direct-mapped virtual" -- a pointer already valid in the +// higher-half direct map; dereference it as-is. +// The doc comment on each field states which one it is. +// ==================================================================== + +namespace montauk::boot { + + // The version of THIS contract that the kernel was compiled against. + // An adapter stamps the contract revision it produced into + // BootInfo::contractVersion; Boot.cpp refuses to continue on mismatch. + // Bump this whenever the meaning or layout of BootInfo changes. + static constexpr uint32_t ContractVersion = 1; + + // ---------------------------------------------------------------- + // Optional-capability advertisement. + // A bit set in BootInfo::features means the corresponding sub-struct + // has been populated and may be used. A bit clear means the kernel + // must behave as if that information does not exist. + // ---------------------------------------------------------------- + enum Feature : uint32_t { + FeatureFramebuffer = 1u << 0, // BootInfo::framebuffer is valid + FeatureRsdp = 1u << 1, // BootInfo::rsdpPhysical is valid + FeatureModules = 1u << 2, // BootInfo::modules is valid + FeatureEfiSystemTable = 1u << 3, // BootInfo::efi.systemTablePhysical valid + FeatureEfiMemoryMap = 1u << 4, // BootInfo::efi memory-map fields valid + FeatureSmp = 1u << 5, // BootInfo::smp is valid (>1 CPU available) + }; + + // ---------------------------------------------------------------- + // Physical memory map. + // ---------------------------------------------------------------- + // + // Normalised classification of a physical memory region. Adapters map + // their bootloader's native types onto these. The kernel only strictly + // distinguishes Usable from everything else (the page-frame allocator + // claims Usable regions), but the full taxonomy is preserved so future + // code (e.g. reclaiming BootloaderReclaimable, honouring AcpiNvs across + // S3) has the information it needs. + enum class MemoryKind : uint32_t { + Usable, // free RAM the kernel may allocate from + Reserved, // firmware/hardware reserved; never touch + AcpiReclaimable, // ACPI tables; reclaimable after parsing + AcpiNvs, // ACPI non-volatile storage; preserve + BadMemory, // known-faulty RAM; never use + BootloaderReclaimable, // bootloader structures; reclaimable post-boot + KernelAndModules, // the kernel image and loaded modules + Framebuffer, // the linear framebuffer region + Unknown, // adapter could not classify; treat as Reserved + }; + + struct MemoryRegion { + uint64_t base; // physical base address (page-aligned) + uint64_t length; // length in bytes (page-multiple) + MemoryKind kind; + }; + + struct MemoryMap { + MemoryRegion* regions; // HHDM/direct-mapped virtual; `count` entries + uint64_t count; // number of valid entries + }; + + // ---------------------------------------------------------------- + // Linear framebuffer (valid iff FeatureFramebuffer). + // The kernel's console and graphics stack render directly into this. + // ---------------------------------------------------------------- + struct Framebuffer { + uint64_t address; // HHDM/direct-mapped virtual base of the + // pixel buffer (write pixels here directly) + uint64_t width; // visible width in pixels + uint64_t height; // visible height in pixels + uint64_t pitch; // bytes per scanline (>= width*bpp/8) + uint16_t bpp; // bits per pixel (typically 32) + + // RGB channel placement within a pixel, as size+shift pairs. + // value = ((channel & ((1<= sizeof(desc)) + uint32_t descriptorVersion; // EFI memory descriptor version + }; + + // ---------------------------------------------------------------- + // Symmetric multiprocessing (valid iff FeatureSmp). + // ---------------------------------------------------------------- + // + // The contract models AP startup as a capability rather than a data + // snapshot, because waking an application processor is an ACTION that + // only the bootloader (which parked the AP in a known state) can + // perform. The kernel fills in `extraArgument` for each CPU it wants + // to bring up, then calls SmpInfo::startCpu. The bootloader resumes + // the parked AP such that it begins executing `entry(cpu)` on a stack + // the bootloader provides, with the same handoff machine state the BSP + // received (long mode, paging on, kernel mapped). `entry` recovers its + // per-CPU context from `cpu->extraArgument`. + struct BootCpu; + + struct SmpInfo { + uint64_t cpuCount; // total CPUs reported (including the BSP) + uint32_t bspLapicId; // local-APIC ID of the bootstrap processor + + BootCpu* cpus; // HHDM/direct-mapped virtual; `cpuCount` entries. + // The entry whose lapicId == bspLapicId is the BSP + // and must NOT be started (it is already running). + + // Wake `cpu`, causing it to begin executing `entry(cpu)`. Returns + // true if the start request was accepted. `entry` runs on a + // bootloader-supplied stack; it never returns. Implemented by the + // active adapter. Safe to call once per AP. + bool (*startCpu)(BootCpu* cpu, void (*entry)(BootCpu*)); + }; + + struct BootCpu { + uint32_t lapicId; // local-APIC ID of this CPU + uint64_t extraArgument; // free for the kernel: stash a per-CPU + // pointer here before calling startCpu; it is + // handed back to `entry` via `cpu->extraArgument`. + void (*entry)(BootCpu*); // set by startCpu; do not touch directly + void* native; // adapter-private back-reference; opaque to + // the kernel + }; + + // ---------------------------------------------------------------- + // The complete boot contract, owned by the kernel. + // ---------------------------------------------------------------- + struct BootInfo { + uint32_t contractVersion; // MUST equal ContractVersion + uint32_t features; // bitmask of Feature + const char* loaderName; // human-readable, e.g. "Limine"; never null + + // ---- Always required (no feature bit; absence is a fatal error) ---- + uint64_t hhdmBase; // virtual base of the higher-half direct map: + // virtual = physical + hhdmBase for all RAM + MemoryMap memoryMap; // the physical memory map + + // ---- Optional, guarded by `features` ---- + uint64_t rsdpPhysical; // FeatureRsdp: physical addr of ACPI RSDP + Framebuffer framebuffer; // FeatureFramebuffer + ModuleList modules; // FeatureModules + EfiInfo efi; // FeatureEfiSystemTable / FeatureEfiMemoryMap + SmpInfo smp; // FeatureSmp + + bool has(Feature f) const { return (features & f) != 0; } + }; + +} diff --git a/kernel/src/Boot/BootProtocol.hpp b/kernel/src/Boot/BootProtocol.hpp new file mode 100644 index 0000000..2035a34 --- /dev/null +++ b/kernel/src/Boot/BootProtocol.hpp @@ -0,0 +1,100 @@ +/* + * BootProtocol.hpp + * The Montauk Boot Contract: handoff state + adapter interface + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include "BootInfo.hpp" + +// ==================================================================== +// The Montauk Boot Contract -- Part 1: the HANDOFF STATE +// ==================================================================== +// +// BootInfo.hpp specifies the DATA the bootloader must produce. This file +// specifies the MACHINE STATE the bootloader must establish before it +// transfers control to the kernel, and the single function an adapter must +// implement to produce the BootInfo. +// +// A conforming bootloader MUST, before jumping to the kernel entry point +// (the ELF entry symbol `kmain`), guarantee the following on the bootstrap +// processor. These mirror the guarantees the kernel currently relies on; +// a novel "Montauk Loader" need only reproduce them to be a drop-in. +// +// x86_64 handoff state: +// --------------------- +// * CPU is in 64-bit long mode, CPL 0, interrupts DISABLED (IF=0). +// * Paging is ENABLED with a valid 4-level (or 5-level) page table in +// CR3. The page table must contain: +// - The kernel image, mapped at its linked higher-half virtual +// addresses (this kernel links at 0xffffffff80000000; see +// linker-x86_64.ld) with appropriate per-segment permissions. +// - A Higher-Half Direct Map (HHDM): all usable physical RAM (and +// the framebuffer / firmware regions the kernel must reach) +// mapped linearly at `physical + BootInfo::hhdmBase`. The kernel +// resolves its own physical load address by walking CR3, so the +// kernel's higher-half pages must resolve to physical frames. +// * A valid, naturally-aligned stack of at least a few KiB, with +// RSP 16-byte aligned per the SysV ABI at the point of entry. +// * GDT with valid 64-bit code/data descriptors loaded. (The kernel +// installs its own GDT/IDT immediately, so the bootloader's table +// need only be valid enough to survive the first instructions.) +// * A20 enabled, SSE/CR0/CR4 in a sane state (the kernel re-enables +// SSE itself; it must not fault before then). +// * The control register/MSR state required for the above (EFER.LME, +// CR4.PAE, CR0.PG/PE) set consistently. +// +// Application processors (APs): +// ---------------------------- +// APs need not be running on entry. They must be parked such that a +// later call to SmpInfo::startCpu (see BootInfo.hpp) can resume each +// one into the SAME handoff state described above, on a +// bootloader-supplied stack, executing the kernel-provided entry. +// +// Firmware tables: +// --------------- +// If the platform is UEFI, EFI runtime services must still be callable +// (the relevant regions discoverable through BootInfo::efi); the +// bootloader must NOT have called ExitBootServices in a way that +// invalidates runtime services, and must report the RSDP it found. +// +// ==================================================================== +// The Montauk Boot Contract -- Part 2: the ADAPTER INTERFACE +// ==================================================================== +// +// Each supported bootloader provides exactly ONE translation unit (an +// "adapter", e.g. Protocols/LimineProtocol.cpp) that: +// +// (a) emits whatever request/handshake structures that bootloader's +// protocol requires (kept entirely inside the adapter so the rest of +// the kernel never sees bootloader-specific types), and +// +// (b) implements `montauk::boot::Acquire()` below, translating the +// bootloader's native responses into a Montauk-native BootInfo. +// +// Linking two adapters into one kernel is a link-time error (duplicate +// definition of Acquire), which is intentional: a kernel build targets one +// boot protocol. To support a new bootloader, add a new adapter .cpp and +// build against it instead. +// ==================================================================== + +namespace montauk::boot { + + // Implemented by the active bootloader adapter. + // + // Populates `out` with everything the kernel needs. Returns true on + // success. Returns false if the boot environment is unusable or the + // bootloader's protocol revision is unsupported -- in which case the + // kernel halts (it cannot meaningfully run). + // + // Contract for the implementer: + // * Set out.contractVersion = ContractVersion. + // * Set out.loaderName to a non-null human-readable string. + // * Always populate hhdmBase and memoryMap (required). + // * Set the Feature bit for, and populate, every optional block the + // bootloader provided; leave the bit clear otherwise. + // * Perform NO kernel logging and allocate NO kernel memory: this runs + // before the page-frame allocator and heap exist. Translate only. + bool Acquire(BootInfo& out); + +} diff --git a/kernel/src/Boot/Protocols/LimineProtocol.cpp b/kernel/src/Boot/Protocols/LimineProtocol.cpp new file mode 100644 index 0000000..b3a2881 --- /dev/null +++ b/kernel/src/Boot/Protocols/LimineProtocol.cpp @@ -0,0 +1,300 @@ +/* + * LimineProtocol.cpp + * Montauk Boot Contract adapter for the Limine boot protocol + * Copyright (c) 2026 Daniel Hammer, Limine Contributors (request layout) +*/ + +// ==================================================================== +// Limine boot-protocol adapter. +// +// This is the ONLY translation unit in the kernel that includes +// or knows anything about Limine. It does two jobs: +// +// 1. Emits the Limine request structures (the markers, base revision, and +// one request per piece of data we need). Limine fills in the +// `.response` pointers before jumping to the kernel. +// +// 2. Implements montauk::boot::Acquire(), translating those native +// responses into the bootloader-agnostic BootInfo contract. +// +// To port MontaukOS to a different bootloader, write a sibling file in this +// directory that emits that loader's handshake and implements Acquire(), and +// build against it instead of this one. Nothing else in the kernel changes. +// ==================================================================== + +#include "../BootProtocol.hpp" +#include + +// -------------------------------------------------------------------- +// Limine requests. These were historically in Platform/Limine.hpp; they +// now live here so the rest of the kernel never sees Limine types. +// +// Requests must survive dead-code elimination and live in the +// .limine_requests section, hence "used" + the section attribute. The +// start/end markers and base-revision tag must appear exactly once in the +// linked image -- this file is that one place. +// -------------------------------------------------------------------- + +namespace { + + __attribute__((used, section(".limine_requests"))) + volatile LIMINE_BASE_REVISION(3); + + __attribute__((used, section(".limine_requests"))) + volatile limine_framebuffer_request framebuffer_request = { + .id = LIMINE_FRAMEBUFFER_REQUEST, + .revision = 0, + .response = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_efi_system_table_request system_table_request = { + .id = LIMINE_EFI_SYSTEM_TABLE_REQUEST, + .revision = 0, + .response = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_hhdm_request hhdm_request = { + .id = LIMINE_HHDM_REQUEST, + .revision = 0, + .response = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_memmap_request memmap_request = { + .id = LIMINE_MEMMAP_REQUEST, + .revision = 0, + .response = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_efi_memmap_request efi_memmap_request = { + .id = LIMINE_EFI_MEMMAP_REQUEST, + .revision = 0, + .response = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_rsdp_request rsdp_request = { + .id = LIMINE_RSDP_REQUEST, + .revision = 0, + .response = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_module_request module_request = { + .id = LIMINE_MODULE_REQUEST, + .revision = 1, + .response = nullptr, + .internal_module_count = 0, + .internal_modules = nullptr + }; + + __attribute__((used, section(".limine_requests"))) + volatile limine_mp_request mp_request = { + .id = LIMINE_MP_REQUEST, + .revision = 0, + .response = nullptr, + .flags = 0 + }; + + __attribute__((used, section(".limine_requests_start"))) + volatile LIMINE_REQUESTS_START_MARKER; + + __attribute__((used, section(".limine_requests_end"))) + volatile LIMINE_REQUESTS_END_MARKER; + +} + +// -------------------------------------------------------------------- +// Static, bootloader-independent storage for the translated contract arrays. +// +// Acquire() runs before the page-frame allocator and heap exist, so it +// cannot allocate. We translate Limine's native arrays into these fixed +// buffers (kernel .bss). Caps are generous relative to real hardware; if a +// machine ever exceeds them we clamp (and the SMP path clamps again to +// MaxCPUs), which is far better than allocating from a non-existent heap. +// -------------------------------------------------------------------- + +namespace { + using namespace montauk::boot; + + constexpr uint64_t kMaxRegions = 512; // UEFI maps are typically < 256 + constexpr uint64_t kMaxModules = 32; + constexpr uint64_t kMaxCpus = 256; // SmpBoot re-clamps to MaxCPUs (64) + + MemoryRegion g_regions[kMaxRegions]; + Module g_modules[kMaxModules]; + BootCpu g_cpus[kMaxCpus]; + + MemoryKind TranslateKind(uint64_t limineType) { + switch (limineType) { + case LIMINE_MEMMAP_USABLE: return MemoryKind::Usable; + case LIMINE_MEMMAP_RESERVED: return MemoryKind::Reserved; + case LIMINE_MEMMAP_ACPI_RECLAIMABLE: return MemoryKind::AcpiReclaimable; + case LIMINE_MEMMAP_ACPI_NVS: return MemoryKind::AcpiNvs; + case LIMINE_MEMMAP_BAD_MEMORY: return MemoryKind::BadMemory; + case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: return MemoryKind::BootloaderReclaimable; + // Renamed KERNEL_AND_MODULES -> EXECUTABLE_AND_MODULES at Limine + // API revision 3; accept whichever this build exposes. +#ifdef LIMINE_MEMMAP_KERNEL_AND_MODULES + case LIMINE_MEMMAP_KERNEL_AND_MODULES: return MemoryKind::KernelAndModules; +#else + case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: return MemoryKind::KernelAndModules; +#endif + case LIMINE_MEMMAP_FRAMEBUFFER: return MemoryKind::Framebuffer; + default: return MemoryKind::Unknown; + } + } + + // Trampoline bridging Limine's AP entry convention (called with the + // native limine_mp_info*) to the contract's entry convention (called + // with the BootCpu*). startCpu() stashes the BootCpu* in Limine's + // extra_argument so we can recover it here. + void LimineApTrampoline(limine_mp_info* info) { + BootCpu* cpu = reinterpret_cast(info->extra_argument); + cpu->entry(cpu); + } + + bool LimineStartCpu(BootCpu* cpu, void (*entry)(BootCpu*)) { + auto* info = reinterpret_cast(cpu->native); + if (info == nullptr) return false; + + cpu->entry = entry; + info->extra_argument = reinterpret_cast(cpu); + + // Publish the entry point last, with release semantics, so the + // parked AP observes a fully-initialised BootCpu/extra_argument. + __atomic_store_n(&info->goto_address, + reinterpret_cast(LimineApTrampoline), + __ATOMIC_SEQ_CST); + return true; + } +} + +namespace montauk::boot { + + bool Acquire(BootInfo& out) { + // Refuse to run under a Limine revision we do not understand. + if (!LIMINE_BASE_REVISION_SUPPORTED) { + return false; + } + + out.contractVersion = ContractVersion; + out.features = 0; + out.loaderName = "Limine"; + + // ---- Required: HHDM ---- + if (hhdm_request.response == nullptr) { + return false; // cannot address physical memory without this + } + out.hhdmBase = hhdm_request.response->offset; + + // ---- Required: physical memory map ---- + // If absent we leave the map empty; the kernel reports the failure + // with a visible Panic once the console is up (a silent halt here + // would give the user no diagnostic). + if (memmap_request.response != nullptr + && memmap_request.response->entry_count > 0) { + auto* resp = memmap_request.response; + uint64_t count = resp->entry_count; + if (count > kMaxRegions) count = kMaxRegions; + for (uint64_t i = 0; i < count; i++) { + limine_memmap_entry* e = resp->entries[i]; + g_regions[i].base = e->base; + g_regions[i].length = e->length; + g_regions[i].kind = TranslateKind(e->type); + } + out.memoryMap.regions = g_regions; + out.memoryMap.count = count; + } + + // ---- Optional: framebuffer ---- + if (framebuffer_request.response != nullptr + && framebuffer_request.response->framebuffer_count >= 1) { + limine_framebuffer* fb = framebuffer_request.response->framebuffers[0]; + out.framebuffer.address = reinterpret_cast(fb->address); + out.framebuffer.width = fb->width; + out.framebuffer.height = fb->height; + out.framebuffer.pitch = fb->pitch; + out.framebuffer.bpp = fb->bpp; + out.framebuffer.redMaskSize = fb->red_mask_size; + out.framebuffer.redMaskShift = fb->red_mask_shift; + out.framebuffer.greenMaskSize = fb->green_mask_size; + out.framebuffer.greenMaskShift = fb->green_mask_shift; + out.framebuffer.blueMaskSize = fb->blue_mask_size; + out.framebuffer.blueMaskShift = fb->blue_mask_shift; + out.features |= FeatureFramebuffer; + } + + // ---- Optional: ACPI RSDP (physical address) ---- + if (rsdp_request.response != nullptr) { + out.rsdpPhysical = rsdp_request.response->address; + out.features |= FeatureRsdp; + } + + // ---- Optional: boot modules / ramdisk ---- + if (module_request.response != nullptr + && module_request.response->module_count > 0) { + auto* resp = module_request.response; + uint64_t count = resp->module_count; + if (count > kMaxModules) count = kMaxModules; + for (uint64_t i = 0; i < count; i++) { + limine_file* f = resp->modules[i]; + g_modules[i].address = f->address; + g_modules[i].size = f->size; + // Limine API revision >= 3 names the tag field `string`. + g_modules[i].name = (f->string != nullptr) ? f->string : ""; + } + out.modules.modules = g_modules; + out.modules.count = count; + out.features |= FeatureModules; + } + + // ---- Optional: UEFI system table (physical address) ---- + if (system_table_request.response != nullptr + && system_table_request.response->address != 0) { + out.efi.systemTablePhysical = system_table_request.response->address; + out.features |= FeatureEfiSystemTable; + } + + // ---- Optional: UEFI memory map (for runtime-services mapping) ---- + if (efi_memmap_request.response != nullptr + && efi_memmap_request.response->memmap != nullptr) { + auto* resp = efi_memmap_request.response; + out.efi.memoryMap = resp->memmap; + out.efi.memoryMapSize = resp->memmap_size; + out.efi.descriptorSize = resp->desc_size; + out.efi.descriptorVersion = static_cast(resp->desc_version); + out.features |= FeatureEfiMemoryMap; + } + + // ---- Optional: SMP / application processors ---- + if (mp_request.response != nullptr + && mp_request.response->cpu_count > 1) { + auto* resp = mp_request.response; + uint64_t count = resp->cpu_count; + if (count > kMaxCpus) count = kMaxCpus; + for (uint64_t i = 0; i < count; i++) { + limine_mp_info* info = resp->cpus[i]; + g_cpus[i].lapicId = info->lapic_id; + g_cpus[i].extraArgument = 0; + g_cpus[i].entry = nullptr; + g_cpus[i].native = info; + } + out.smp.cpuCount = count; + out.smp.bspLapicId = resp->bsp_lapic_id; + out.smp.cpus = g_cpus; + out.smp.startCpu = &LimineStartCpu; + out.features |= FeatureSmp; + } else { + out.smp.cpuCount = 1; + out.smp.cpus = nullptr; + out.smp.startCpu = nullptr; + } + + return true; + } + +} diff --git a/kernel/src/Efi/UEFI.hpp b/kernel/src/Efi/UEFI.hpp index c1b4a0d..0de859d 100644 --- a/kernel/src/Efi/UEFI.hpp +++ b/kernel/src/Efi/UEFI.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include #include #include @@ -273,7 +273,7 @@ namespace Efi { inline EFI_RESET_SYSTEM g_ResetSystem = nullptr; - inline void Init(SystemTable* ST, limine_efi_memmap_response* efiMemmap) { + inline void Init(SystemTable* ST, const montauk::boot::EfiInfo& efi) { Kt::KernelLogStream(Kt::OK, "UEFI") << "ST Minor Revision: " << ST->Header.Revision.MinorRevision; Kt::KernelLogStream(Kt::OK, "UEFI") << "ST Major Revision: " << ST->Header.Revision.MajorRevision; @@ -285,7 +285,7 @@ namespace Efi { /* Identity-map EFI runtime service regions so firmware code can reference its own data at physical addresses */ if (Memory::VMM::g_paging) { - Memory::VMM::g_paging->MapEfiRuntime(efiMemmap); + Memory::VMM::g_paging->MapEfiRuntime(efi); } EFI_TIME Time; diff --git a/kernel/src/Fs/Boot.cpp b/kernel/src/Fs/Boot.cpp index a30a7d9..6535dc5 100644 --- a/kernel/src/Fs/Boot.cpp +++ b/kernel/src/Fs/Boot.cpp @@ -28,26 +28,26 @@ namespace Fs { return *lhs == *rhs; } - bool InitializeRamdiskFromModules(const volatile limine_module_response* moduleResponse) { - if (moduleResponse == nullptr || moduleResponse->module_count == 0) { + bool InitializeRamdiskFromModules(const montauk::boot::ModuleList& modules) { + if (modules.modules == nullptr || modules.count == 0) { Kt::KernelLogStream(Kt::WARNING, "Modules") << "No modules loaded (ramdisk unavailable)"; return false; } Kt::KernelLogStream(Kt::OK, "Modules") - << "Found " << (uint64_t)moduleResponse->module_count << " module(s)"; + << "Found " << modules.count << " module(s)"; bool hasRamdisk = false; - for (uint64_t i = 0; i < moduleResponse->module_count; i++) { - limine_file* module = moduleResponse->modules[i]; - if (module == nullptr || !StringsEqual(module->string, "ramdisk")) { + for (uint64_t i = 0; i < modules.count; i++) { + const montauk::boot::Module& module = modules.modules[i]; + if (!StringsEqual(module.name, "ramdisk")) { continue; } Kt::KernelLogStream(Kt::OK, "Modules") - << "Ramdisk module at " << kcp::hex << (uint64_t)module->address - << kcp::dec << ", size=" << module->size; - Ramdisk::Initialize(module->address, module->size); + << "Ramdisk module at " << kcp::hex << (uint64_t)module.address + << kcp::dec << ", size=" << module.size; + Ramdisk::Initialize(module.address, module.size); hasRamdisk = true; } @@ -71,8 +71,8 @@ namespace Fs { } - void InitializeBootFilesystems(const volatile limine_module_response* moduleResponse) { - bool hasRamdisk = InitializeRamdiskFromModules(moduleResponse); + void InitializeBootFilesystems(const montauk::boot::ModuleList& modules) { + bool hasRamdisk = InitializeRamdiskFromModules(modules); Vfs::Initialize(); if (hasRamdisk) { diff --git a/kernel/src/Fs/Boot.hpp b/kernel/src/Fs/Boot.hpp index 9e12d3a..d801260 100644 --- a/kernel/src/Fs/Boot.hpp +++ b/kernel/src/Fs/Boot.hpp @@ -5,10 +5,10 @@ */ #pragma once -#include +#include namespace Fs { - void InitializeBootFilesystems(const volatile limine_module_response* moduleResponse); + void InitializeBootFilesystems(const montauk::boot::ModuleList& modules); } diff --git a/kernel/src/Graphics/Cursor.cpp b/kernel/src/Graphics/Cursor.cpp index 373e069..8926081 100644 --- a/kernel/src/Graphics/Cursor.cpp +++ b/kernel/src/Graphics/Cursor.cpp @@ -18,11 +18,11 @@ namespace Graphics::Cursor { static uint64_t g_FbHeight = 0; static uint64_t g_FbPitch = 0; // in bytes - void Initialize(limine_framebuffer* framebuffer) { - g_FbBase = reinterpret_cast(framebuffer->address); - g_FbWidth = framebuffer->width; - g_FbHeight = framebuffer->height; - g_FbPitch = framebuffer->pitch; + void Initialize(const montauk::boot::Framebuffer& framebuffer) { + g_FbBase = reinterpret_cast(framebuffer.address); + g_FbWidth = framebuffer.width; + g_FbHeight = framebuffer.height; + g_FbPitch = framebuffer.pitch; Kt::KernelLogStream(Kt::OK, "Graphics") << "Framebuffer initialized (" << (uint64_t)g_FbWidth << "x" << (uint64_t)g_FbHeight << ")"; diff --git a/kernel/src/Graphics/Cursor.hpp b/kernel/src/Graphics/Cursor.hpp index eae51dd..3183593 100644 --- a/kernel/src/Graphics/Cursor.hpp +++ b/kernel/src/Graphics/Cursor.hpp @@ -6,11 +6,11 @@ #pragma once #include -#include +#include namespace Graphics::Cursor { - void Initialize(limine_framebuffer* framebuffer); + void Initialize(const montauk::boot::Framebuffer& framebuffer); uint32_t* GetFramebufferBase(); uint64_t GetFramebufferWidth(); diff --git a/kernel/src/Hal/SmpBoot.cpp b/kernel/src/Hal/SmpBoot.cpp index 90278c4..1f1cd4d 100644 --- a/kernel/src/Hal/SmpBoot.cpp +++ b/kernel/src/Hal/SmpBoot.cpp @@ -17,11 +17,7 @@ #include #include #include -#include - -// Defined in Platform/Limine.hpp (included only by Main.cpp to avoid -// duplicating the LIMINE_BASE_REVISION tag). -extern volatile limine_mp_request mp_request; +#include #include // Verify assembly offsets match struct layout @@ -153,14 +149,15 @@ namespace Smp { // ==================================================================== // AP entry point - // Called by Limine when goto_address is written. - // RDI = pointer to limine_mp_info for this CPU. - // Runs on a 64KiB Limine-provided stack. + // Invoked by the boot contract (montauk::boot::SmpInfo::startCpu) when + // the AP is woken. Receives the BootCpu for this processor; the kernel + // stashed this CPU's CpuData* in BootCpu::extraArgument before starting + // it. Runs on a bootloader-provided stack. // ==================================================================== - static void ApEntry(limine_mp_info* info) { - // Find our CpuData (stored in extra_argument by BootAPs) - CpuData* cpu = (CpuData*)info->extra_argument; + static void ApEntry(montauk::boot::BootCpu* bootCpu) { + // Find our CpuData (stashed in extraArgument by BootAPs) + CpuData* cpu = (CpuData*)bootCpu->extraArgument; // --- Load per-CPU GDT --- Hal::GDTPointer gdtPtr { @@ -223,17 +220,16 @@ namespace Smp { // Boot all APs // ==================================================================== - void BootAPs() { - if (mp_request.response == nullptr) { - KernelLogStream(WARNING, "SMP") << "No MP response from bootloader - single CPU mode"; + void BootAPs(const montauk::boot::SmpInfo& smp) { + if (smp.cpus == nullptr || smp.startCpu == nullptr) { + KernelLogStream(WARNING, "SMP") << "No SMP info from bootloader - single CPU mode"; return; } - auto* resp = mp_request.response; - uint64_t cpuCount = resp->cpu_count; + uint64_t cpuCount = smp.cpuCount; KernelLogStream(INFO, "SMP") << "Bootloader reports " << cpuCount << " CPU(s), BSP LAPIC ID " - << (uint64_t)resp->bsp_lapic_id; + << (uint64_t)smp.bspLapicId; if (cpuCount <= 1) { KernelLogStream(INFO, "SMP") << "Single CPU system - no APs to boot"; @@ -251,23 +247,25 @@ namespace Smp { // - Each AP's init is purely local (GDT, TSS, APIC, MSRs) int apIndex = 1; // BSP is index 0 for (uint64_t i = 0; i < cpuCount; i++) { - limine_mp_info* info = resp->cpus[i]; + montauk::boot::BootCpu& info = smp.cpus[i]; - if (info->lapic_id == resp->bsp_lapic_id) continue; + if (info.lapicId == smp.bspLapicId) continue; if (apIndex >= MaxCPUs) break; CpuData& ap = g_cpus[apIndex]; ap.selfPtr = (uint64_t)≈ ap.cpuIndex = apIndex; - ap.lapicId = info->lapic_id; + ap.lapicId = info.lapicId; ap.currentSlot = -1; ap.started = false; SetupPerCpuGdtTss(ap); - info->extra_argument = (uint64_t)≈ - // Wake this AP (it runs ApEntry in parallel with other APs) - __atomic_store_n(&info->goto_address, (limine_goto_address)ApEntry, __ATOMIC_SEQ_CST); + // Stash this AP's CpuData* where ApEntry will recover it, then + // ask the bootloader to wake the AP into ApEntry. APs run in + // parallel with each other. + info.extraArgument = (uint64_t)≈ + smp.startCpu(&info, ApEntry); apIndex++; } diff --git a/kernel/src/Hal/SmpBoot.hpp b/kernel/src/Hal/SmpBoot.hpp index f3b5591..abf4723 100644 --- a/kernel/src/Hal/SmpBoot.hpp +++ b/kernel/src/Hal/SmpBoot.hpp @@ -7,6 +7,7 @@ #pragma once #include #include +#include // ==================================================================== // Assembly-visible offsets into CpuData @@ -60,6 +61,8 @@ namespace Smp { // Initialize BSP per-CPU data (call before interrupts are enabled) void InitBsp(); - // Boot all Application Processors (call after all subsystems ready) - void BootAPs(); + // Boot all Application Processors (call after all subsystems ready). + // `smp` is the boot contract's SMP block; if FeatureSmp was not + // advertised the caller may still pass it (cpuCount<=1 is a no-op). + void BootAPs(const montauk::boot::SmpInfo& smp); } diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 1c2696a..fbed18c 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -58,33 +57,33 @@ extern "C" uint64_t KernelStartSymbol; extern "C" uint64_t KernelEndSymbol; extern "C" void kmain() { - if (LIMINE_BASE_REVISION_SUPPORTED == false) { - Hal::Halt(); - } - // Call global constructors. for (std::size_t i = 0; &__init_array[i] != __init_array_end; i++) { __init_array[i](); } - if (framebuffer_request.response == nullptr - || framebuffer_request.response->framebuffer_count < 1) { + // Acquire the boot environment through the Montauk Boot Contract. The + // active bootloader adapter (see Boot/Protocols/) translates its native + // handoff into this bootloader-agnostic structure. A false return means + // we cannot even bring up a console (unsupported loader, no HHDM, or no + // framebuffer) -- there is nothing to do but halt. + if (!montauk::boot::Initialize()) { Hal::Halt(); } - - limine_framebuffer *framebuffer{framebuffer_request.response->framebuffers[0]}; + const montauk::boot::BootInfo& boot = montauk::boot::Info(); + const montauk::boot::Framebuffer& framebuffer = boot.framebuffer; Kt::Initialize( - (uint32_t*)framebuffer->address, - framebuffer->width, - framebuffer->height, - framebuffer->pitch, - framebuffer->red_mask_size, - framebuffer->red_mask_shift, - framebuffer->green_mask_size, - framebuffer->green_mask_shift, - framebuffer->blue_mask_size, - framebuffer->blue_mask_shift + (uint32_t*)framebuffer.address, + framebuffer.width, + framebuffer.height, + framebuffer.pitch, + framebuffer.redMaskSize, + framebuffer.redMaskShift, + framebuffer.greenMaskSize, + framebuffer.greenMaskShift, + framebuffer.blueMaskSize, + framebuffer.blueMaskShift ); @@ -95,15 +94,14 @@ extern "C" void kmain() { Hal::EnableSSE(); #endif - uint64_t hhdm_offset = hhdm_request.response->offset; - Memory::HHDMBase = hhdm_offset; + Memory::HHDMBase = boot.hhdmBase; - if (memmap_request.response == nullptr) { + if (boot.memoryMap.regions == nullptr || boot.memoryMap.count == 0) { Panic("System memory map missing!", nullptr); } Kt::KernelLogStream(OK, "Mem") << "Creating PageFrameAllocator"; - Memory::PageFrameAllocator pmm(Memory::Scan(memmap_request.response)); + Memory::PageFrameAllocator pmm(Memory::Scan(boot.memoryMap)); Memory::g_pfa = &pmm; Kt::KernelLogStream(OK, "Mem") << "Creating HeapAllocator"; @@ -118,7 +116,7 @@ extern "C" void kmain() { Memory::VMM::Paging g_paging{}; Memory::VMM::g_paging = &g_paging; - g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), memmap_request.response); + g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), boot.memoryMap, framebuffer); // Reprogram PAT so entry 1 = Write-Combining (default is Write-Through). // Must be done after paging init and before any WC mappings. @@ -137,7 +135,7 @@ extern "C" void kmain() { Graphics::Cursor::MapWriteCombining(); #endif - Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(rsdp_request.response->address)); + Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(boot.rsdpPhysical)); #if defined (__x86_64__) if (g_acpi.GetXSDT() != nullptr) { @@ -174,10 +172,13 @@ extern "C" void kmain() { } #endif - Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(system_table_request.response->address); - Efi::Init(ST, efi_memmap_request.response); + // UEFI runtime services are optional (absent on legacy-BIOS boots). + if (boot.has(montauk::boot::FeatureEfiSystemTable)) { + Efi::SystemTable* ST = (Efi::SystemTable*)Memory::HHDM(boot.efi.systemTablePhysical); + Efi::Init(ST, boot.efi); + } - Fs::InitializeBootFilesystems(module_request.response); + Fs::InitializeBootFilesystems(boot.modules); // A Bluetooth adapter present at boot enumerates during the xHCI port scan, // before the ramdisk is mounted. Now that drive 0 is up, finish any @@ -191,7 +192,7 @@ extern "C" void kmain() { Ipc::Initialize(); // Boot Application Processors (all subsystems ready, APs can schedule) - Smp::BootAPs(); + Smp::BootAPs(boot.smp); // Flush any stale PS/2 mouse bytes that accumulated during boot // (edge-triggered IRQs can be lost while spinlocks disable interrupts) diff --git a/kernel/src/Memory/Memmap.cpp b/kernel/src/Memory/Memmap.cpp index b015f25..52ba775 100644 --- a/kernel/src/Memory/Memmap.cpp +++ b/kernel/src/Memory/Memmap.cpp @@ -7,21 +7,21 @@ using namespace Kt; namespace Memory { - LargestSection Scan(limine_memmap_response* mmap) { + LargestSection Scan(const montauk::boot::MemoryMap& mmap) { LargestSection currentLargestSection{}; - for (size_t i = 0; i < mmap->entry_count; i++) { - auto entry = mmap->entries[i]; + for (size_t i = 0; i < mmap.count; i++) { + const auto& entry = mmap.regions[i]; - if (entry->base == 0) { + if (entry.base == 0) { continue; } - - if (entry->type == LIMINE_MEMMAP_USABLE) { - if (entry->length > currentLargestSection.size) { + + if (entry.kind == montauk::boot::MemoryKind::Usable) { + if (entry.length > currentLargestSection.size) { currentLargestSection = { - .address = (uint64_t)entry->base, - .size = entry->length + .address = entry.base, + .size = entry.length }; } } @@ -33,4 +33,4 @@ namespace Memory { return currentLargestSection; } -}; \ No newline at end of file +}; diff --git a/kernel/src/Memory/Memmap.hpp b/kernel/src/Memory/Memmap.hpp index 6c26e46..6d2360c 100644 --- a/kernel/src/Memory/Memmap.hpp +++ b/kernel/src/Memory/Memmap.hpp @@ -1,8 +1,7 @@ #pragma once -#include +#include #include - -using namespace std; +#include namespace Memory { // Shared @@ -11,5 +10,7 @@ namespace Memory { size_t size; }; - LargestSection Scan(limine_memmap_response* mmap); -}; \ No newline at end of file + // Scan the boot contract's physical memory map for the largest single + // run of usable RAM (the page-frame allocator is seeded from it). + LargestSection Scan(const montauk::boot::MemoryMap& mmap); +}; diff --git a/kernel/src/Memory/Paging.cpp b/kernel/src/Memory/Paging.cpp index 8df2ad4..b123b47 100644 --- a/kernel/src/Memory/Paging.cpp +++ b/kernel/src/Memory/Paging.cpp @@ -23,7 +23,8 @@ namespace Memory::VMM { PML4 = (PageTable*)SubHHDM((PageTable*)Memory::g_pfa->AllocateZeroed()); } - void Paging::Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap) { + void Paging::Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, const montauk::boot::MemoryMap& memMap, + const montauk::boot::Framebuffer& framebuffer) { // Map kernel Kt::KernelLogStream(Kt::DEBUG, "VMM") << "Paging::Init called with kernelBaseVirt as 0x" << base::hex << kernelBaseVirt; @@ -31,15 +32,31 @@ namespace Memory::VMM { Map(GetPhysKernelAddress(pageAddr), pageAddr); } - // Map HHDM: find the highest physical address and map everything - // from 0 to that point. This covers gaps between memory map entries - // (e.g. BIOS ROM at 0xE0000) that firmware may not list but the - // kernel still needs to access via HHDM. - + // Map HHDM: map physical memory contiguously from 0 up to the top of + // real backing store, so the kernel can reach any RAM/firmware byte at + // phys+hhdmBase. Mapping from 0 (rather than per-region) also covers + // unlisted low gaps -- BIOS ROM at 0xE0000, the EBDA, etc. -- that the + // kernel still touches through the HHDM. + // + // The upper bound is computed over real backing store ONLY: RAM, ACPI + // tables, modules, the framebuffer, and so on. Reserved/Unknown + // regions are deliberately EXCLUDED from the bound. Firmware commonly + // reports the 64-bit PCI MMIO aperture as a Reserved region hundreds of + // GiB above RAM (its base scales with the CPU's physical-address width); + // letting that inflate maxPhysAddr would make this loop allocate + // gigabytes of 4 KiB page tables and exhaust the frame allocator (OOM). + // Such MMIO is mapped on demand via MapMMIO with the right cache + // attributes -- never through the HHDM -- so it is correct to leave it + // out here. Reserved regions that sit *below* the bound (e.g. low BIOS + // areas) are still mapped by the contiguous fill. uint64_t maxPhysAddr = 0; - for (size_t i = 0; i < memMap->entry_count; i++) { - auto entry = memMap->entries[i]; - uint64_t entryEnd = entry->base + entry->length; + for (size_t i = 0; i < memMap.count; i++) { + const auto& entry = memMap.regions[i]; + if (entry.kind == montauk::boot::MemoryKind::Reserved + || entry.kind == montauk::boot::MemoryKind::Unknown) { + continue; + } + uint64_t entryEnd = entry.base + entry.length; if (entryEnd > maxPhysAddr) maxPhysAddr = entryEnd; } maxPhysAddr = (maxPhysAddr + 0xFFF) & ~0xFFFULL; @@ -48,6 +65,24 @@ namespace Memory::VMM { Map(pageAddr, HHDM(pageAddr)); } + // The linear framebuffer can sit in a PCI BAR above the RAM bound we + // just mapped (e.g. in the 32-bit MMIO hole), and it is not guaranteed + // to appear as a memory-map region. It MUST be reachable through the + // HHDM before we switch to these page tables, because the very next log + // line (and PAT setup, and Cursor init) renders to it via phys+hhdmBase. + // Map its pages explicitly (write-back for now; Cursor upgrades them to + // write-combining once PAT is reprogrammed). + if (framebuffer.address != 0) { + uint64_t fbPhys = Memory::SubHHDM(framebuffer.address); + uint64_t fbBytes = framebuffer.height * framebuffer.pitch; + uint64_t fbPages = (fbBytes + 0xFFF) / 0x1000; + fbPhys &= ~0xFFFULL; + for (uint64_t p = 0; p < fbPages; p++) { + uint64_t phys = fbPhys + p * 0x1000; + Map(phys, HHDM(phys)); + } + } + LoadCR3(PML4); Kt::KernelLogStream(Kt::OK, "VMM") << "Switched CR3"; } @@ -455,12 +490,12 @@ namespace Memory::VMM { return GetPhysAddr((std::uint64_t)PML4, virtualAddress, false); } - void Paging::MapEfiRuntime(limine_efi_memmap_response* efiMemmap) { - if (!efiMemmap) return; + void Paging::MapEfiRuntime(const montauk::boot::EfiInfo& efi) { + if (efi.memoryMap == nullptr || efi.descriptorSize == 0) return; - auto* base = (uint8_t*)efiMemmap->memmap; - uint64_t descSize = efiMemmap->desc_size; - uint64_t count = efiMemmap->memmap_size / descSize; + auto* base = (uint8_t*)efi.memoryMap; + uint64_t descSize = efi.descriptorSize; + uint64_t count = efi.memoryMapSize / descSize; struct EfiMemDesc { uint32_t Type; diff --git a/kernel/src/Memory/Paging.hpp b/kernel/src/Memory/Paging.hpp index b15db33..0582a36 100644 --- a/kernel/src/Memory/Paging.hpp +++ b/kernel/src/Memory/Paging.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include #include +#include #include namespace Memory::VMM { @@ -87,7 +88,8 @@ public: PageTable* PML4{}; Paging(); - void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap); + void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, const montauk::boot::MemoryMap& memMap, + const montauk::boot::Framebuffer& framebuffer); void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress); void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress); void MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress); @@ -121,7 +123,7 @@ public: // Identity-map EFI runtime service regions so firmware code can // reference its own data at physical addresses. - void MapEfiRuntime(limine_efi_memmap_response* efiMemmap); + void MapEfiRuntime(const montauk::boot::EfiInfo& efi); }; extern Paging* g_paging; diff --git a/kernel/src/Platform/Limine.hpp b/kernel/src/Platform/Limine.hpp deleted file mode 100644 index 09167ca..0000000 --- a/kernel/src/Platform/Limine.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Limine.hpp - * Limine platform definitions and support - * Copyright (c) Limine Contributors (via Limine C++ example) -*/ - -#include "../limine.h" - -// Set the base revision to 3, this is recommended as this is the latest -// base revision described by the Limine boot protocol specification. -// See specification for further info. - -namespace { - - __attribute__((used, section(".limine_requests"))) - volatile LIMINE_BASE_REVISION(3); - - } - - // The Limine requests can be placed anywhere, but it is important that - // the compiler does not optimise them away, so, usually, they should - // be made volatile or equivalent, _and_ they should be accessed at least - // once or marked as used with the "used" attribute as done here. - - namespace { - - __attribute__((used, section(".limine_requests"))) - volatile limine_framebuffer_request framebuffer_request = { - .id = LIMINE_FRAMEBUFFER_REQUEST, - .revision = 0, - .response = nullptr - }; - - __attribute__((used, section(".limine_requests"))) - volatile limine_efi_system_table_request system_table_request = { - .id = LIMINE_EFI_SYSTEM_TABLE_REQUEST, - .revision = 0, - .response = nullptr - }; - - __attribute__((used, section(".limine_requests"))) - volatile limine_hhdm_request hhdm_request = { - .id = LIMINE_HHDM_REQUEST, - .revision = 0, - .response = nullptr - }; - - __attribute__((used, section(".limine_requests"))) - volatile limine_memmap_request memmap_request = { - .id = LIMINE_MEMMAP_REQUEST, - .revision = 0, - .response = nullptr - }; - - __attribute__((used, section(".limine_requests"))) - volatile limine_efi_memmap_request efi_memmap_request = { - .id = LIMINE_EFI_MEMMAP_REQUEST, - .revision = 0, - .response = nullptr - }; - - __attribute__((used, section(".limine_requests"))) - volatile limine_rsdp_request rsdp_request = { - .id = LIMINE_RSDP_REQUEST, - .revision = 0, - .response = nullptr - }; - - __attribute__((used, section(".limine_requests"))) - volatile limine_module_request module_request = { - .id = LIMINE_MODULE_REQUEST, - .revision = 1, - .response = nullptr, - .internal_module_count = 0, - .internal_modules = nullptr - }; - - } - - // MP request is outside the anonymous namespace so SmpBoot.cpp can - // reference it via extern. - __attribute__((used, section(".limine_requests"))) - volatile limine_mp_request mp_request = { - .id = LIMINE_MP_REQUEST, - .revision = 0, - .response = nullptr, - .flags = 0 - }; - - // Finally, define the start and end markers for the Limine requests. - // These can also be moved anywhere, to any .cpp file, as seen fit. - - namespace { - - __attribute__((used, section(".limine_requests_start"))) - volatile LIMINE_REQUESTS_START_MARKER; - - __attribute__((used, section(".limine_requests_end"))) - volatile LIMINE_REQUESTS_END_MARKER; - -} - \ No newline at end of file diff --git a/programs/src/btlist/main.cpp b/programs/src/btlist/main.cpp new file mode 100644 index 0000000..fcf37cc --- /dev/null +++ b/programs/src/btlist/main.cpp @@ -0,0 +1,36 @@ +/* + * main.cpp + * uses the bt_list syscall to output a list of connected Bluetooth devices. + * Copyright (c) 2026 Daniel Hammer +*/ +#include +#include + +const char* addr_to_string(char* str, uint8_t* bdAddr) { + snprintf(str, 18, "%02x:%02x:%02x:%02x:%02x:%02x", + bdAddr[0], bdAddr[1], bdAddr[2], bdAddr[3], bdAddr[4], bdAddr[5] + ); + + return (const char*)str; +} + +extern "C" void _start() { + montauk::abi::BtDevInfo devices[64] = { }; + int n = montauk::bt_list(devices, 64); + + if (n <= 0) { + montauk::print("No Bluetooth devices are currently connected. For a list of paired devices, use 'btbonds'.\n"); + montauk::exit(0); + } + + montauk::print(" MAC addr. Encrypted\n"); + + for (int i = 0; i < n; i++) { + char macAddrString[18] = {}; + addr_to_string(macAddrString, devices[i].bdAddr); + + printf(" %-17s %s\n", macAddrString, devices[i].encrypted ? "True" : "False"); + } + + montauk::exit(0); +} \ No newline at end of file