//! The **loader ↔ kernel** contract: everything a bootloader (boot/, e.g. efi.zig //! built as BOOTX64.efi) and the kernel (system/kernel/kernel.zig) must agree on to //! hand control over — the handoff structures the loader fills in, plus the kernel's //! virtual-memory layout and the physical↔virtual addressing both sides use. //! //! Both binaries import this as the `boot-handoff` module, so the layout is defined //! in exactly one place. **User space never sees this** — the kernel↔user contract is //! [[abi]] (system/abi.zig); device types are [[device-abi]] (system/devices/device-abi.zig). const std = @import("std"); /// Calling convention for the bootloader→kernel jump. Pinned to SystemV so it does /// not depend on each binary's target default: the UEFI bootloader's C /// convention is Microsoft x64 (first arg in RCX), the freestanding kernel's is /// SystemV (first arg in RDI). Both reference this to agree on where `*BootInformation` /// is passed. pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} }; /// Pixel byte order of the linear framebuffer the firmware handed us. pub const PixelFormat = enum(u32) { /// Byte 0 = Red, 1 = Green, 2 = Blue, 3 = reserved. rgbx, /// Byte 0 = Blue, 1 = Green, 2 = Red, 3 = reserved. bgrx, }; /// A linear framebuffer: `width`x`height` pixels, each a 32-bit value, with /// `pitch` bytes between the start of one row and the next (which may be larger /// than `width * 4` due to hardware padding). /// /// A `base` of 0 means **no framebuffer** — the firmware exposed no Graphics /// Output Protocol (a headless server, say). The kernel must treat on-screen /// output as optional and never assume a framebuffer exists. pub const Framebuffer = extern struct { base: usize, // the memory address where pixel data starts (0 = none) width: u32, // visible pixels per row (e.g. 1920) height: u32, // visible rows (e.g. 1080) pitch: u32, // bytes from the start of one row to the start of the next format: PixelFormat, /// Whether a usable framebuffer was handed over. pub fn present(self: Framebuffer) bool { return self.base != 0 and self.width != 0 and self.height != 0; } }; /// The kernel's virtual-memory layout (higher-half). The kernel is linked at /// `kernel_virt_base` but loaded at a low physical address; all of RAM (and the /// device MMIO windows) is also mapped at `physmap_base + physical`, so the kernel /// can reach any physical address by adding a constant. The low half is left /// entirely to user space. /// /// user image + stack : 0x0000_7000_0000_0000 (PML4[224], low half) /// kernel heap : 0xFFFF_8000_0000_0000 (PML4[256]) /// physmap : 0xFFFF_8800_0000_0000 (PML4[272]) + physical /// kernel image : 0xFFFF_FFFF_8000_0000 (PML4[511]) pub const physmap_base: u64 = 0xFFFF_8800_0000_0000; pub const kernel_virt_base: u64 = 0xFFFF_FFFF_8000_0000; /// Physical address -> its virtual address in the physmap. The single way the /// kernel dereferences a physical address once paging is up. /// /// **Hazard:** valid only once the (bootstrap or final) page tables are live. /// The bootloader may use the *constant* `physmap_base` to build those tables, /// but must not call this to dereference memory before its own CR3 is loaded — /// it runs under the firmware's identity map, where these addresses are unmapped. pub inline fn physicalToVirtual(physical: u64) u64 { return physical + physmap_base; } /// Physmap virtual address -> physical. Inverse of `physicalToVirtual`; for producing /// the physical address of something the kernel holds a physmap pointer to /// (e.g. a page-table frame for CR3, a post-mortem breadcrumb's RAM location). pub inline fn virtualToPhysical(virtual: u64) u64 { return virtual - physmap_base; } /// danos's own classification of a span of physical memory — deliberately not /// UEFI's vocabulary. Each boot path (UEFI now, device tree later) translates its /// native memory description into these kinds, so the kernel never learns what /// booted it. [[architecture]] keeps the same discipline for CPU code. pub const MemoryKind = enum(u32) { /// Free RAM the kernel may allocate. Each boot path folds its own transient /// memory into this once it's genuinely free (e.g. the UEFI loader classifies /// boot-services memory as usable after ExitBootServices), so the kernel never /// has to know about boot-protocol-specific "reclaimable" states. usable, /// Firmware, MMIO, the kernel image, our own boot buffers, the boot stack — /// never hand out. reserved, /// ACPI tables: parse, then reclaim. acpi_tables, /// ACPI non-volatile storage: preserve across sleep, do not allocate. acpi_nvs, /// Not backed by RAM: memory-mapped device registers or a reserved /// address-space window (e.g. PCIe configuration space). Kept distinct from /// `reserved` so RAM accounting doesn't count device address space. mmio, }; /// One contiguous span of physical memory. Because danos defines this layout /// itself (unlike the UEFI descriptor it's built from), `@sizeOf` is /// authoritative — the kernel walks a plain `[]MemoryRegion`, with none of the /// firmware's variable descriptor-stride to worry about. pub const MemoryRegion = extern struct { base: u64, // physical start address pages: u64, // length in 4 KiB pages (the [[abi]] `page_size` unit) kind: MemoryKind, _pad: u32 = 0, }; /// The physical memory layout handed to the kernel: a pointer to an array of /// `len` `MemoryRegion`s, in a buffer that outlives the loader. pub const MemoryMap = extern struct { regions: usize, // address of a `[len]MemoryRegion` len: usize, }; /// One PT_LOAD segment of the kernel image, so the kernel can re-map itself with /// correct permissions (code R+X, rodata R, data R+W+NX). `flags` are raw ELF /// segment flags: PF_X=1, PF_W=2, PF_R=4. `virtual` is the higher-half link address; /// `physical` is where the loader actually placed the segment (they differ once the /// kernel links high — the loader records the real load address here). pub const KernelSegment = extern struct { virtual: u64, physical: u64, pages: u64, flags: u32, _pad: u32 = 0, }; /// Handoff structure the bootloader fills in and passes to the kernel's /// `_start` in RDI (the first argument under the SystemV AMD64 C ABI). pub const BootInformation = extern struct { framebuffer: Framebuffer, memory_map: MemoryMap, /// The kernel's own PT_LOAD segments (it has three: text, rodata, data). kernel_segments: [8]KernelSegment, kernel_segment_count: u32, /// Physical address of the ACPI RSDP the firmware exposed, or 0 if none. The /// kernel's device layer parses the ACPI tables from here to discover hardware. /// A device-tree boot path leaves this 0 and (later) fills a `device_tree_blob` /// field instead, so the kernel discovers devices without knowing what booted it. acpi_rsdp: u64 = 0, /// The raw `/system/services/init` ELF image, read off the boot volume by the loader /// into memory that survives the handoff (classified reserved, so the kernel /// identity-maps it and never allocates over it). 0/0 = no init found — the /// kernel boots without user space. Grows into a full initial_ramdisk handoff later. init_base: u64 = 0, init_len: u64 = 0, /// The initial_ramdisk image (a bundle of extra user binaries — the VFS server and /// device drivers), read off the boot volume into memory that survives the /// handoff, same as `init` above. 0/0 = no initial_ramdisk. See system/initial-ramdisk.zig. initial_ramdisk_base: u64 = 0, initial_ramdisk_len: u64 = 0, };