Skip to content

Commit 3b14311

Browse files
author
T. Andrew Davis
committed
Refactor bootloader and platform initialization for improved stack handling and GDT management
1 parent c5a7b16 commit 3b14311

7 files changed

Lines changed: 186 additions & 99 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,6 @@ iso9660 = { path = "iso9660", package = "iso9660-rs" }
4444
morpheus-ui = { path = "ui" }
4545
morpheus-helix = { path = "helix" }
4646

47-
[profile.release]
48-
opt-level = "z" # Optimize for size
49-
lto = true # Link-time optimization
50-
codegen-units = 1 # Better optimization
51-
panic = "abort" # No unwinding
52-
strip = true # Strip symbols
5347

5448
[profile.dev]
5549
panic = "abort" # No unwinding in dev either

bootloader/src/baremetal.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ pub unsafe fn enter_baremetal(config: BaremetalEntryConfig) -> ! {
264264
descriptor_version: DESC_VER,
265265
image_base,
266266
image_pages,
267+
stack_base,
268+
stack_pages: STACK_PAGES as u64,
267269
};
268270

269271
let platform = match morpheus_hwinit::platform_init_selfcontained(hwinit_cfg) {

hwinit/asm/cpu/ap_trampoline.s

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ ap_start:
3030
mov ss, ax
3131
xor sp, sp ; stack at top of segment (wraps to 0xFFFF)
3232

33-
; ── load the GDT pointer from the data area ──────────────────────────
34-
; data area is at this_page + 0xF00. Our segment base = 0x8000.
35-
; so offset within segment = 0xF00 + 0x20 (TD_GDT_PTR within data area)
36-
lgdt [0xF20] ; 0x8000 + 0xF20 in linear addressing = offset 0xF20 from segment base
33+
; ── load a TEMPORARY GDT that lives inside this trampoline page ──────
34+
; The BSP's GDT is above 4 GB (PE BSS at 0x140xxxxxx). In 16-bit
35+
; mode, lgdt only reads a 4-byte base → truncated → wrong GDT → #GP.
36+
; We use a temp GDT at a known sub-1MB address for the mode transition,
37+
; then reload the real 64-bit GDT once we're in long mode.
38+
lgdt [0xE38] ; segment offset → physical 0x8E38 (temp_gdt_ptr_16)
3739

3840
; ── enter protected mode ──────────────────────────────────────────────
3941
mov eax, cr0
@@ -77,8 +79,10 @@ ap_pm32:
7779
or eax, (1 << 31) ; CR0.PG
7880
mov cr0, eax
7981

80-
; far jump to 64-bit long mode code
81-
jmp dword 0x08:ap_lm64
82+
; far jump to 64-bit long mode code.
83+
; selector 0x18 = temp GDT's 64-bit code descriptor (L=1, D=0).
84+
; NOT 0x08 — that's the 32-bit code descriptor we used to get here.
85+
jmp dword 0x18:ap_lm64
8286

8387
; ───────────────────────────────────────────────────────────────────────────
8488
; 64-bit long mode
@@ -93,6 +97,25 @@ ap_lm64:
9397
mov ss, ax
9498
; intentionally skip gs — Rust will set GS base via MSR
9599

100+
; ── reload the BSP's ACTUAL 64-bit GDT from the data area ────────────
101+
; In 64-bit mode lgdt reads the full 10-byte descriptor (2+8 base).
102+
; The BSP's GDT is in PE BSS above 4 GB — now we can load it properly.
103+
lgdt [0x8F20] ; TD_GDT_PTR (flat 64-bit address)
104+
105+
; reload data segments again with the real GDT's selectors
106+
mov ax, 0x10
107+
mov ds, ax
108+
mov es, ax
109+
mov fs, ax
110+
mov ss, ax
111+
112+
; reload CS via a far return trick — push selector:offset, retfq
113+
lea rax, [rel .cs_reloaded]
114+
push 0x08 ; kernel code selector from BSP's GDT
115+
push rax
116+
retfq
117+
.cs_reloaded:
118+
96119
; ── load the per-AP stack from data area ──────────────────────────────
97120
mov rsp, qword [0x8F10] ; TD_STACK
98121

@@ -105,8 +128,68 @@ ap_lm64:
105128
jmp rax ; ap_rust_entry(core_idx, lapic_id) — never returns
106129

107130
; ───────────────────────────────────────────────────────────────────────────
108-
; Pad to keep total code well under 0xF00 (data area starts there)
131+
; TEMP GDT (offset 0xE00) — used for the real→prot→long mode transition.
132+
;
133+
; The BSP's GDT lives in PE BSS above 4 GB. A 16-bit lgdt can only read
134+
; a 4-byte base, truncating 0x140xxxxxx → garbage. This temp GDT at
135+
; physical 0x8E00 is safely below 1 MB.
136+
;
137+
; We need TWO code descriptors:
138+
; 0x08 — 32-bit code (D=1, L=0): for protected mode transition.
139+
; D=1 gives 32-bit default operand size so `bits 32` instructions
140+
; decode correctly. If D=0 the CPU interprets `or eax, imm32`
141+
; as `or ax, imm16` and the instruction stream desynchronizes.
142+
; yes this is what was causing the triple fault. yes it was that dumb.
143+
; 0x18 — 64-bit code (L=1, D=0): for the far jump into long mode.
144+
; Intel requires L=1 D=0 for 64-bit mode.
145+
;
146+
; After reaching 64-bit mode we reload the BSP's real GDT (which has its
147+
; own code64 at selector 0x08) and retfq to it.
109148
; ───────────────────────────────────────────────────────────────────────────
149+
times (0xE00 - ($ - $$)) db 0
150+
151+
; 0xE00: temp GDT entries
152+
temp_gdt:
153+
; 0x00: null descriptor
154+
dq 0
155+
156+
; 0x08: 32-bit code (D=1, L=0, P=1, DPL=0, type=exec+read)
157+
; used ONLY for the real→protected mode transition.
158+
dw 0xFFFF ; limit low (4GB with G=1)
159+
dw 0x0000 ; base low
160+
db 0x00 ; base mid
161+
db 0x9A ; P=1, DPL=0, S=1, type=0xA (exec/read)
162+
db 0xCF ; G=1, D=1, L=0, AVL=0, limit_hi=0xF
163+
db 0x00 ; base high
164+
165+
; 0x10: flat data (P=1, DPL=0, writable, G=1, D=1, 4GB limit)
166+
dw 0xFFFF ; limit low
167+
dw 0x0000 ; base low
168+
db 0x00 ; base mid
169+
db 0x92 ; P=1, DPL=0, S=1, type=0x2 (data/write)
170+
db 0xCF ; G=1, D=1, L=0, AVL=0, limit_hi=0xF
171+
db 0x00 ; base high
172+
173+
; 0x18: 64-bit code (L=1, D=0, P=1, DPL=0, type=exec+read)
174+
; used for the far jump into long mode after LME+PG are set.
175+
dw 0x0000 ; limit low (ignored in 64-bit mode)
176+
dw 0x0000 ; base low
177+
db 0x00 ; base mid
178+
db 0x9A ; P=1, DPL=0, S=1, type=0xA (exec/read)
179+
db 0x20 ; G=0, L=1, D=0, AVL=0, limit_hi=0x0
180+
db 0x00 ; base high
181+
temp_gdt_end:
182+
183+
; padding to 0xE38
184+
times (0xE38 - ($ - $$)) db 0
185+
186+
; 0xE38: temp GDT pointer for 16-bit lgdt (6 bytes: limit + 32-bit base)
187+
; segment offset 0xE38 → physical 0x8E38. lgdt reads 6 bytes in 16-bit mode.
188+
temp_gdt_ptr_16:
189+
dw (temp_gdt_end - temp_gdt - 1) ; limit = 31 (4 entries × 8 - 1)
190+
dd 0x00008E00 ; base = physical address of temp_gdt
191+
192+
; pad to 0xF00
110193
times (0xF00 - ($ - $$)) db 0
111194

112195
; ───────────────────────────────────────────────────────────────────────────

hwinit/asm/cpu/context_switch.s

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,11 @@ irq_timer_isr:
121121
.skip_fxsave:
122122

123123
; ── ACK LAPIC (write 0 to EOI register) ──────────────────────────────
124-
xor eax, eax
125-
mov dword [LAPIC_EOI_ADDR], eax
124+
; 0xFEE000B0 has bit 31 set. [imm32] in 64-bit mode sign-extends to
125+
; 0xFFFFFFFFFEE000B0. load into r11d: zero-extends, gives correct ptr.
126+
; r11 is already saved at [rsp+0x50] so clobbering it here is safe.
127+
mov r11d, LAPIC_EOI_ADDR ; r11 = 0x00000000FEE000B0
128+
mov dword [r11], 0 ; EOI
126129

127130
; ── Call scheduler_tick(current_ctx: *const CpuContext) ──────────────
128131
; MS x64 ABI: first arg in RCX. Need 32-byte shadow space on stack.

hwinit/src/platform.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ pub struct SelfContainedConfig {
5555
pub image_base: u64,
5656
/// Number of 4 KiB pages the PE image occupies (derived from SizeOfImage).
5757
pub image_pages: u64,
58+
/// Physical base of the boot stack allocated by the bootloader.
59+
/// The entire range [stack_base, stack_base + stack_pages * 4096)
60+
/// is excluded from the buddy allocator.
61+
pub stack_base: u64,
62+
/// Number of 4 KiB pages in the boot stack allocation.
63+
pub stack_pages: u64,
5864
}
5965

6066
/// Platform configuration input (legacy - externally allocated).
@@ -184,14 +190,30 @@ pub unsafe fn platform_init_selfcontained(
184190
puts("\n");
185191
}
186192

187-
// 4) Boot stack pages (current RSP ± safety margin)
193+
// 4) Boot stack pages — use actual bounds from bootloader, not RSP guess.
194+
// The bootloader passes the exact base + page count it allocated.
195+
// Guessing from RSP missed the bottom of the stack and let the buddy
196+
// write FreeNode headers into live LoaderData pages — OVMF's 0xAF
197+
// scrub then looked like corruption when those nodes were walked.
188198
let boot_stack_base;
189199
let boot_stack_top;
190-
{
200+
if config.stack_base != 0 && config.stack_pages != 0 {
201+
boot_stack_base = config.stack_base;
202+
boot_stack_top = config.stack_base + config.stack_pages * 4096;
203+
let mut p = boot_stack_base;
204+
while p < boot_stack_top && hw_count < hw_holes.len() {
205+
hw_holes[hw_count] = p;
206+
hw_count += 1;
207+
p += 4096;
208+
}
209+
} else {
210+
// fallback: RSP-based guess (generous margins)
191211
let rsp: u64;
192-
core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nostack, nomem));
193-
boot_stack_base = (rsp & !0xFFF).saturating_sub(128 * 1024);
194-
boot_stack_top = (rsp & !0xFFF) + 32 * 1024;
212+
unsafe {
213+
core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nostack, nomem));
214+
}
215+
boot_stack_base = (rsp & !0xFFF).saturating_sub(256 * 1024);
216+
boot_stack_top = (rsp + 0xFFF) & !0xFFF;
195217
let mut p = boot_stack_base;
196218
while p < boot_stack_top && hw_count < hw_holes.len() {
197219
hw_holes[hw_count] = p;
@@ -451,6 +473,11 @@ pub unsafe fn platform_init_selfcontained(
451473
}
452474
checkpoint("phase10.5-reserve-pt");
453475
crate::paging::reserve_page_table_pages();
476+
// second validation: catch corruption introduced by carve_block splits
477+
{
478+
let reg = global_registry_mut();
479+
reg.validate_free_lists();
480+
}
454481
checkpoint("phase10.5-done");
455482

456483
// phase 11: filesystem

hwinit/src/process/scheduler.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -741,18 +741,6 @@ pub unsafe extern "C" fn scheduler_tick(current_ctx: &CpuContext) -> &'static Cp
741741
result
742742
}
743743
// USER PROCESS SPAWN
744-
745-
/// Spawn a Ring 3 user thread in the caller's address space.
746-
///
747-
/// Shares the parent's CR3 (same page tables, same heap, same mmap state).
748-
/// Gets its own kernel stack, its own slot in PROCESS_TABLE, and starts
749-
/// execution at `entry` with `rdi = arg` and `rsp = stack_top`.
750-
///
751-
/// The caller must provide a valid, mapped user stack. Use SYS_MMAP to
752-
/// allocate one before calling this.
753-
///
754-
/// I had fun with this.
755-
///
756744
/// Returns the new thread's PID (which also serves as the TID).
757745
pub unsafe fn spawn_user_thread(entry: u64, stack_top: u64, arg: u64) -> Result<u32, &'static str> {
758746
use crate::cpu::gdt::{USER_CS, USER_DS};

0 commit comments

Comments
 (0)