feat: introduce bootloader-agnostic Boot Contract, add btlist command to list connected Bluetooth devices
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 129
|
||||
#define MONTAUK_BUILD_NUMBER 132
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* BootInfo.hpp
|
||||
* The Montauk Boot Contract (MBC)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// ====================================================================
|
||||
// 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<<size)-1)) << shift).
|
||||
uint8_t redMaskSize, redMaskShift;
|
||||
uint8_t greenMaskSize, greenMaskShift;
|
||||
uint8_t blueMaskSize, blueMaskShift;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Boot modules / ramdisk (valid iff FeatureModules).
|
||||
// The kernel locates its ramdisk by matching `name`.
|
||||
// ----------------------------------------------------------------
|
||||
struct Module {
|
||||
void* address; // HHDM/direct-mapped virtual base of the blob
|
||||
uint64_t size; // size of the blob in bytes
|
||||
const char* name; // null-terminated identifier the loader was
|
||||
// asked to tag this module with (e.g. "ramdisk").
|
||||
// May be empty but never null.
|
||||
};
|
||||
|
||||
struct ModuleList {
|
||||
Module* modules; // HHDM/direct-mapped virtual; `count` entries
|
||||
uint64_t count;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// UEFI services (fields valid per FeatureEfiSystemTable /
|
||||
// FeatureEfiMemoryMap). Absent on legacy-BIOS boots.
|
||||
// ----------------------------------------------------------------
|
||||
struct EfiInfo {
|
||||
// Physical address of the EFI_SYSTEM_TABLE. Reach it via
|
||||
// Memory::HHDM(systemTablePhysical). Valid iff FeatureEfiSystemTable.
|
||||
uint64_t systemTablePhysical;
|
||||
|
||||
// The raw UEFI memory map, exactly as returned by GetMemoryMap().
|
||||
// Used to discover EFI runtime-services regions. All fields valid
|
||||
// iff FeatureEfiMemoryMap.
|
||||
void* memoryMap; // HHDM/direct-mapped virtual; array of
|
||||
// descriptors, each `descriptorSize` bytes
|
||||
uint64_t memoryMapSize; // total bytes in `memoryMap`
|
||||
uint64_t descriptorSize; // bytes per descriptor (>= 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; }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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 <limine.h>
|
||||
// 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.h>
|
||||
|
||||
// --------------------------------------------------------------------
|
||||
// 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<BootCpu*>(info->extra_argument);
|
||||
cpu->entry(cpu);
|
||||
}
|
||||
|
||||
bool LimineStartCpu(BootCpu* cpu, void (*entry)(BootCpu*)) {
|
||||
auto* info = reinterpret_cast<limine_mp_info*>(cpu->native);
|
||||
if (info == nullptr) return false;
|
||||
|
||||
cpu->entry = entry;
|
||||
info->extra_argument = reinterpret_cast<uint64_t>(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<limine_goto_address>(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<uint64_t>(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<uint32_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <limine.h>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Timekeeping/Time.hpp>
|
||||
@@ -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;
|
||||
|
||||
+11
-11
@@ -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) {
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <limine.h>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
|
||||
namespace Fs {
|
||||
|
||||
void InitializeBootFilesystems(const volatile limine_module_response* moduleResponse);
|
||||
void InitializeBootFilesystems(const montauk::boot::ModuleList& modules);
|
||||
|
||||
}
|
||||
|
||||
@@ -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<uint32_t*>(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<uint32_t*>(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 << ")";
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <limine.h>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
|
||||
namespace Graphics::Cursor {
|
||||
|
||||
void Initialize(limine_framebuffer* framebuffer);
|
||||
void Initialize(const montauk::boot::Framebuffer& framebuffer);
|
||||
|
||||
uint32_t* GetFramebufferBase();
|
||||
uint64_t GetFramebufferWidth();
|
||||
|
||||
+21
-23
@@ -17,11 +17,7 @@
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Timekeeping/ApicTimer.hpp>
|
||||
#include <limine.h>
|
||||
|
||||
// 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 <Boot/BootInfo.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
// 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++;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
|
||||
// ====================================================================
|
||||
// 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);
|
||||
}
|
||||
|
||||
+31
-30
@@ -6,7 +6,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <limine.h>
|
||||
#include <Boot/Boot.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Efi/UEFI.hpp>
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <Memory/Memmap.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Platform/Limine.hpp>
|
||||
#include <Platform/Util.hpp>
|
||||
#include <Hal/IDT.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#pragma once
|
||||
#include <limine.h>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
#include <cstddef>
|
||||
|
||||
using namespace std;
|
||||
#include <cstdint>
|
||||
|
||||
namespace Memory {
|
||||
// Shared
|
||||
@@ -11,5 +10,7 @@ namespace Memory {
|
||||
size_t size;
|
||||
};
|
||||
|
||||
LargestSection Scan(limine_memmap_response* mmap);
|
||||
};
|
||||
// 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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include <limine.h>
|
||||
#include <Boot/BootInfo.hpp>
|
||||
#include <cstdint>
|
||||
#include <stddef.h>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user