Skip to content

High‐Level Interaction Overview: Creating a Custom Bootloader, UEFI Integration, and Custom Kernel in Rust

vketteni edited this page Jun 10, 2024 · 1 revision

Creating a custom bootloader, integrating UEFI, and developing a custom kernel in Rust for a bare-metal environment involves several high-level steps and interactions. Here’s an overview of how these components interact with each other:

High-Level Interaction Overview

UEFI Firmware Initialization

  1. Power-On/Reset: When the system powers on, the UEFI firmware initializes the hardware components (CPU, memory, peripheral devices) and performs POST (Power-On Self-Test).
  2. Firmware Initialization: UEFI firmware loads its environment and prepares the system for the boot process.

Custom Bootloader

  1. UEFI Entry Point: The UEFI firmware hands control to the bootloader, usually through a standardized entry point defined by the UEFI specification.
  2. Boot Services Access: The bootloader uses UEFI boot services to access hardware components, read from storage devices, and gather system information.
  3. Load Kernel: The bootloader locates the kernel binary on the storage device, loads it into memory, and prepares the environment for kernel execution.
  4. ExitBootServices: Before transferring control to the kernel, the bootloader calls ExitBootServices to signal the end of UEFI boot services, freeing up resources and transitioning control.

Custom Kernel

  1. Kernel Entry Point: The kernel’s entry point is called by the bootloader. At this stage, the CPU is in a well-defined state, and the memory layout is set up by the bootloader.
  2. Initialize Hardware: The kernel performs additional hardware initialization if needed, sets up its own environment (e.g., page tables for memory management), and initializes kernel subsystems (scheduler, interrupt handlers, etc.).
  3. Start Kernel Services: The kernel starts essential services and enters its main loop, where it begins executing user-space applications or handling system tasks.

Detailed Steps

1. UEFI Firmware Initialization

  • Power-On: System is powered on, and UEFI firmware initializes the CPU, memory, and basic hardware components.
  • POST: UEFI performs POST to ensure hardware is functioning correctly.
  • Firmware Setup: UEFI sets up its runtime environment, prepares boot services, and displays a boot menu if needed.

2. Custom Bootloader

UEFI Entry Point

The bootloader is launched by UEFI. In Rust, this typically involves defining a UEFI entry function using the uefi crate.

use uefi::prelude::*;

#[entry]
fn main(image_handle: Handle, system_table: SystemTable<Boot>) -> Status {
    // Bootloader code here
    Status::SUCCESS
}

Boot Services Access

The bootloader uses UEFI boot services to perform tasks like reading the kernel binary from disk.

use uefi::proto::media::file::File;
use uefi::proto::media::fs::SimpleFileSystem;

let fs = system_table.boot_services().locate_protocol::<SimpleFileSystem>().unwrap();
let mut file = fs.open_volume().unwrap().open("path/to/kernel", FileMode::Read, FileAttribute::READ_ONLY).unwrap().into_regular_file().unwrap();
let buffer = vec![0; file.get_info::<FileInfo>().unwrap().file_size() as usize];
file.read(&mut buffer).unwrap();

Load Kernel

The bootloader loads the kernel binary into memory and prepares the necessary information (e.g., memory map) to pass to the kernel.

// Load kernel binary into memory (pseudo-code)
let kernel_start_address = 0x100000; // Example address
system_table.boot_services().allocate_pages(
    AllocateType::Address(kernel_start_address),
    MemoryType::LOADER_DATA,
    number_of_pages,
).unwrap();
// Copy kernel binary to the allocated memory

ExitBootServices

The bootloader calls ExitBootServices to transition control to the kernel.

let map_key;
system_table.exit_boot_services(image_handle, map_key).unwrap();

3. Custom Kernel

Kernel Entry Point

The kernel starts executing from its entry point. This is typically defined in Rust as a #[no_mangle] function.

#[no_mangle]
pub extern "C" fn _start() -> ! {
    // Kernel initialization code here
    loop {}
}

Initialize Hardware

The kernel sets up hardware, initializes memory management (e.g., page tables), and other subsystems.

// Initialize hardware components and kernel subsystems

Start Kernel Services

The kernel starts its main loop or scheduler to handle processes and system tasks.

fn main_loop() -> ! {
    loop {
        // Handle system tasks and processes
    }
}

Summary

  • UEFI Firmware initializes the hardware and prepares the system for boot.
  • Custom Bootloader takes control, uses UEFI services to load the kernel, and transitions control to the kernel.
  • Custom Kernel initializes its environment and hardware, then starts executing system tasks and processes.

This high-level interaction ensures a smooth transition from power-on to a fully functional operating system, leveraging the capabilities of UEFI and the safety and performance benefits of Rust.