ling-lang
↓ 0 downloadsLing - The Omniglot Systems Language
Keywords systems, polyglot, ai, crypto, game
lingfu install ling-langCode / Files (995)
crates/ling-kernel/src/arch/x86_64/boot.rs
//! 32-bit-to-long-mode bootstrap for the x86_64/GRUB target.
//!
//! GRUB's Multiboot2 handoff lands in 32-bit protected mode with paging
//! disabled *even for a 64-bit ELF kernel* — that's the Multiboot spec, not
//! a GRUB quirk (confirmed empirically: booting the plain 64-bit `_start`
//! directly triple-faults immediately, CR0/CR4/EFER all showing no long
//! mode / no paging at handoff). Nothing else sets up long mode for us, so
//! this is the real ELF entry point: it builds minimal identity-mapped
//! page tables (2MiB pages covering the full 4GiB 32-bit address space —
//! not just the first 1GiB of RAM: `ahci.rs` needs to reach a PCI BAR's
//! MMIO window, which firmware typically places up near the top of that
//! range, not down in RAM), enables PAE + long mode (EFER.LME) + paging,
//! loads a 64-bit GDT, and far-jumps into `kernel_entry` (defined by the
//! generated per-project `main.rs`, same name/convention as the
//! aarch64/Raspberry Pi boot path in `boot.rs`) — only past that jump is it
//! safe to run any 64-bit-compiled code, including the rest of this crate.
use core::arch::global_asm;
extern "C" {
static stack_top: u8;
static _kernel_end: u8;
}
/// Top of the 512 KiB stack `_start` switched onto before `kernel_entry` —
/// the only stack that exists this early, so it's what `gdt::init` points
/// the TSS's `RSP0` at (used if a future ring-3 interrupt/syscall needs a
/// known-good kernel stack; unused while everything still runs in ring 0).
pub fn boot_stack_top() -> u64 {
(&raw const stack_top) as u64
}
/// First physical address past the kernel's own image (`.text`/`.rodata`/
/// `.data`/`.bss`, page-4KiB-aligned by `KERNEL_LINKER_SCRIPT`) — the
/// low-water mark `mm::frame`'s Multiboot2 memory-map detection clips
/// every usable region against, since a bootloader's memory map has no
/// concept of "already occupied by the kernel it just loaded."
pub fn kernel_end() -> u64 {
(&raw const _kernel_end) as u64
}
// Multiboot2 header: plain (no video tag, GRUB leaves VGA text mode alone —
// what every existing text-console kernel target needs) vs. one that also
// carries a framebuffer request tag (type 5), gated behind the
// `request_framebuffer` feature so only a kernel target with its own
// framebuffer-based renderer opts into it (see the Cargo.toml feature doc
// comment for why this must not be on by default).
#[cfg(all(not(test), not(feature = "request_framebuffer")))]
global_asm!(
r#"
.section .ling_multiboot, "a"
.align 8
multiboot_header:
.long 0xE85250D6
.long 0
.long multiboot_header_end - multiboot_header
.long -(0xE85250D6 + 0 + (multiboot_header_end - multiboot_header))
.align 8
.short 0
.short 0
.long 8
multiboot_header_end:
"#
);
#[cfg(all(not(test), feature = "request_framebuffer"))]
global_asm!(
r#"
.section .ling_multiboot, "a"
.align 8
multiboot_header:
.long 0xE85250D6
.long 0
.long multiboot_header_end - multiboot_header
.long -(0xE85250D6 + 0 + (multiboot_header_end - multiboot_header))
// 0/0/0 means "any resolution/depth GRUB's video BIOS/VBE can give us"
// — `framebuffer.rs` reads back whatever it actually picked from the
// Multiboot2 info structure rather than assuming these numbers were
// honored exactly.
.align 8
.short 5
.short 0
.long 20
.long 0
.long 0
.long 0
.align 8
.short 0
.short 0
.long 8
multiboot_header_end:
"#
);
#[cfg(not(test))]
global_asm!(
r#"
.section .text.boot, "ax"
.code32
.global _start
_start:
cli
lea esp, [stack_top]
// Bare `mov reg, symbol` (no brackets) for "load this label's address"
// is ambiguous across assemblers/dialects; `lea reg, [symbol]` is the
// unambiguous, portable idiom for it, used everywhere below.
lea eax, [pdpt]
or eax, 0x3
mov [pml4], eax
// PDPT[0..4] -> pd0..pd3, each a 512-entry, 2MiB-page table covering
// 1GiB, so the 4 of them together identity-map the entire 4GiB 32-bit
// physical address space.
lea eax, [pd0]
or eax, 0x3
mov [pdpt], eax
lea eax, [pd1]
or eax, 0x3
mov [pdpt + 8], eax
lea eax, [pd2]
or eax, 0x3
mov [pdpt + 16], eax
lea eax, [pd3]
or eax, 0x3
mov [pdpt + 24], eax
// Fill all 4 PDs with consecutive 2MiB pages — eax is the running
// physical address, carried across all four unrolled loops (0 ->
// 0x40000000 -> 0x80000000 -> 0xC0000000 -> wraps to 0 after the last
// entry, which is fine since nothing reads it again after that).
xor eax, eax
lea edi, [pd0]
mov ecx, 512
fill_pd0:
mov edx, eax
or edx, 0x83
mov [edi], edx
add edi, 8
add eax, 0x200000
dec ecx
jnz fill_pd0
lea edi, [pd1]
mov ecx, 512
fill_pd1:
mov edx, eax
or edx, 0x83
mov [edi], edx
add edi, 8
add eax, 0x200000
dec ecx
jnz fill_pd1
lea edi, [pd2]
mov ecx, 512
fill_pd2:
mov edx, eax
or edx, 0x83
mov [edi], edx
add edi, 8
add eax, 0x200000
dec ecx
jnz fill_pd2
lea edi, [pd3]
mov ecx, 512
fill_pd3:
mov edx, eax
or edx, 0x83
mov [edi], edx
add edi, 8
add eax, 0x200000
dec ecx
jnz fill_pd3
lea eax, [pml4]
mov cr3, eax
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
mov ecx, 0xC0000080
rdmsr
or eax, 1 << 8
wrmsr
mov eax, cr0
or eax, 1 << 31
mov cr0, eax
// Enable SSE: Ling's boxed/dynamic value representation NaN-boxes every
// value as an f64 (even a plain integer comparison like `1 == 1` goes
// through it unless a call's arguments specifically get the raw-int
// fast path — see `translate_op_int`), so ordinary kernel .ling source
// emits SSE2 instructions (movq to/from xmm, cvttsd2si, ...) that fault
// with #UD until this is done (confirmed empirically: an actual QEMU
// boot faulted on exactly such an instruction with SSE left disabled).
mov eax, cr0
and eax, 0xFFFFFFFB // clear EM (bit 2): don't emulate/trap FP+SSE
or eax, 0x2 // set MP (bit 1): required alongside EM handling
mov cr0, eax
mov eax, cr4
or eax, (1 << 9) | (1 << 10) // OSFXSR + OSXMMEXCPT
mov cr4, eax
lgdt [gdt64_pointer]
// A direct far jump (`jmp seg:offset`) isn't accepted by the assembler
// Rust's `global_asm!` uses here; push+far-return is the standard
// portable way to reload CS with the 64-bit code segment instead.
push 0x08
lea eax, [long_mode_entry]
push eax
retf
.code64
long_mode_entry:
mov ax, 0
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
// `ebx` still holds GRUB's Multiboot2 info-structure pointer here —
// nothing between `_start` and this point touches it (the page-table
// setup above only ever uses eax/edx/edi/ecx as scratch), and a far
// jump/return only changes CS, not general-purpose registers. Stash it
// in a plain global before it becomes just another scratch register, so
// `framebuffer::init` (called later, from Rust, via `ling_kernel_init`)
// can find the framebuffer tag GRUB filled in for the request above.
mov [mb2_info_ptr], ebx
call kernel_entry
halt_loop:
hlt
jmp halt_loop
.section .rodata
.align 16
gdt64:
.quad 0
.quad 0x00AF9A000000FFFF
gdt64_pointer:
.short . - gdt64 - 1
.long gdt64
.section .bss
.align 4
.global mb2_info_ptr
mb2_info_ptr:
.skip 4
.align 4096
pml4:
.skip 4096
pdpt:
.skip 4096
pd0:
.skip 4096
pd1:
.skip 4096
pd2:
.skip 4096
pd3:
.skip 4096
.align 16
stack_bottom:
// 512KiB. Slot 0 (the desktop loop / main task) never gets one of
// sched.rs's spawned STACKS -- it keeps running on THIS stack. It does far
// more than early boot now: the whole window-manager frame loop plus, on
// demand, post-quantum crypto (opening Messenger calls `my_ling_id()` ->
// `Identity::from_seed`, an ML-DSA-87 keygen that expands a ~57KiB matrix,
// if the background task hasn't cached the identity first; browser TLS is
// similar). The old 64KiB silently overflowed the moment a second, richer
// window (Messenger) was opened -- a corrupted return address faulted with
// a non-canonical rip. This is BSS (zero-filled at boot, not on disk).
.skip 524288
.global stack_top
stack_top:
"#
);