Bare Metal Boot Architecture
⚠️ Status: This document describes planned bare metal support. Currently only Browser (WASM) and WASI targets are implemented.
This document explores options for running AxebergOS on bare metal hardware, separate from the WASM/browser approach.
Current Architecture (WASM)
The current AxebergOS runs in a browser via WebAssembly:
Browser
└── WebAssembly Runtime
└── AxebergOS WASM Module
├── Kernel (in-memory)
├── VFS (in-memory, OPFS persistence)
└── Shell (xterm.js terminal)
Key characteristics: - No direct hardware access - Browser provides I/O (canvas, DOM events) - OPFS for persistence - Fetch API for networking - Sandboxed execution
Bare Metal Options
Option 1: UEFI Application
Build AxebergOS as a UEFI application that runs directly from the bootloader.
Advantages: - Simple boot process - UEFI provides services (filesystem, networking, console) - Works on modern x86_64 and ARM64 hardware - No bootloader needed (IS the bootloader)
Requirements:
- x86_64-unknown-uefi or aarch64-unknown-uefi target
- UEFI protocols for I/O
- EFI system partition
Crates:
- uefi - UEFI runtime services
- uefi-services - Higher-level UEFI helpers
Boot flow:
UEFI Firmware
└── Load axeberg.efi from EFI System Partition
└── UEFI Application (AxebergOS)
├── Use UEFI Simple Text Output for console
├── Use UEFI Simple File System for storage
└── Use UEFI Network Stack for networking
Option 2: Multiboot2 Kernel (BIOS/Legacy)
Build as a Multiboot2-compliant kernel loaded by GRUB2.
Advantages: - Works on older hardware (BIOS boot) - Well-documented standard - GRUB handles boot complexity
Requirements:
- Custom linker script
- Multiboot2 header
- x86_64-unknown-none target
- No_std environment
Crates:
- bootloader - Rust bootloader
- multiboot2 - Multiboot2 info parsing
Boot flow:
BIOS
└── GRUB2
└── Load axeberg.elf
└── Kernel Entry Point
├── Set up GDT, IDT
├── Enable paging
├── Initialize hardware
└── Start scheduler
Option 3: Custom Bootloader
Use the bootloader crate to create a complete bootable image.
Advantages: - Full control over boot process - Single build artifact - Works on both BIOS and UEFI
Requirements:
- bootloader crate as build dependency
- Custom cargo configuration
- Disk image generation
Example Cargo.toml:
[dependencies]
bootloader = "0.11"
[package.metadata.bootloader]
kernel = "target/x86_64-axeberg/release/axeberg"
Architecture Comparison
| Feature | WASM (Browser) | UEFI | Bare Metal |
|---|---|---|---|
| Target | wasm32-unknown-unknown | x86_64-unknown-uefi | x86_64-unknown-none |
| Std library | Partial | No | No |
| Hardware access | None | Via UEFI protocols | Direct |
| Graphics | Canvas/DOM | GOP (UEFI Graphics) | Framebuffer |
| Keyboard | DOM events | UEFI Input | PS/2, USB HID |
| Storage | OPFS | UEFI Filesystem | AHCI, NVMe |
| Networking | Fetch API | UEFI Network | E1000, Virtio |
| Memory alloc | Browser heap | UEFI alloc | Custom allocator |
Shared Code Strategy
To support both WASM and bare metal, structure the code:
src/
├── lib.rs # Core logic (platform-agnostic)
├── kernel/
│ ├── mod.rs # Kernel abstractions
│ ├── process.rs # Process management
│ └── syscall.rs # Syscall interface
├── vfs/ # Virtual filesystem (separate module)
├── platform/
│ ├── mod.rs # Platform trait
│ ├── web.rs # Browser/WASM implementation
│ └── wasi.rs # WASI CLI implementation
└── main.rs # Entry point (cfg-gated)
The Platform trait abstracts hardware differences:
pub trait Platform {
// ===== Terminal Output =====
/// Write text to the terminal
fn write(&mut self, text: &str);
/// Clear the terminal screen
fn clear(&mut self);
/// Get terminal dimensions
fn term_size(&self) -> TermSize;
// ===== Input =====
/// Poll for a key event (non-blocking)
fn poll_key(&mut self) -> Option<KeyEvent>;
// ===== Timing =====
/// Get current time in milliseconds since some epoch
fn now_ms(&self) -> f64;
// ===== Persistence =====
/// Save state to persistent storage
fn save_state(&mut self, data: &[u8]) -> PlatformResult<()>;
/// Load state from persistent storage
fn load_state(&mut self) -> PlatformResult<Option<Vec<u8>>>;
// ===== Lifecycle =====
/// Called each frame/tick of the main loop
fn tick(&mut self) {}
/// Check if the platform wants to exit
fn should_exit(&self) -> bool {
false
}
}
Required Changes for Bare Metal
- No std: Replace std dependencies with no_std alternatives
- Allocator: Implement global allocator (bump, linked list, buddy)
- Panic handler: Custom panic handling
- Entry point: Architecture-specific entry
- Hardware drivers: Console, storage, interrupts
Recommended Path
- Start with UEFI - Simplest path, UEFI provides many services
- Use
bootloadercrate - For true bare metal, this handles boot complexity - Incremental porting - Start with console, then add storage, then networking
Example: Minimal UEFI App
#![no_std]
#![no_main]
use uefi::prelude::*;
#[entry]
fn main(_image: Handle, mut system_table: SystemTable<Boot>) -> Status {
// Clear screen
system_table.stdout().clear().unwrap();
// Print welcome
writeln!(system_table.stdout(), "AxebergOS v0.1.0").unwrap();
writeln!(system_table.stdout(), "Running on UEFI!").unwrap();
// Wait for keypress
system_table.boot_services().wait_for_event(
&mut [system_table.stdin().wait_for_key_event().unwrap()]
).unwrap();
Status::SUCCESS
}
Conclusion
Bare metal support is a significant undertaking but feasible. The recommended approach:
- Keep the current WASM version as the primary target
- Abstract platform-specific code behind traits
- Start with UEFI for the simplest bare metal path
- Gradually add hardware drivers as needed
The core kernel logic (process management, VFS, syscalls) should work unchanged across platforms with proper abstraction.