λ

lumine

UEFI x86_64 bootloader for linux kernel images

settings

commit 2e9e9c67

authordragsbruh <dragsbruh@protonmail.com>
date2026-07-23 12:54:51 +0530
committerdragsbruh <dragsbruh@protonmail.com> (2026-07-23 12:54:51 +0530)
tree1f2ec3a12f0f65b9275944621f16baed99cd364b
parentf35351f8

feat: load images lazily and fix minor bugs

patch diff stats

+78 -41

build.zig modified +1 -1

diff --git a/build.zig b/build.zig
index bf43280d96546f26553649860471c4dea86a4145..cea0536f7c90a2686f7d717078419ac7e7f93ce5 100644
--- a/build.zig
+++ b/build.zig
@@ -67,7 +67,7 @@         qemu_cmd.addArgs(&.{ "-serial", "mon:stdio", "-nographic" });
     }
 
     switch (cpu_arch) {
-        .aarch64 => qemu_cmd.addArgs(&.{ "-M", "virt", "-cpu", "cortex-a57" }),
+        .aarch64 => qemu_cmd.addArgs(&.{ "-M", "virt", "-cpu", "cortex-a57", "-device", "ramfb" }),
         .x86_64 => {},
     }
 

src/boot/Chainload.zig modified +8 -15

diff --git a/src/boot/Chainload.zig b/src/boot/Chainload.zig
index dff377991104e3c37c5ecdcc3e5b9d62a26ff3cb..f1372716bd437fc1837245c950a6302e6103fafb 100644
--- a/src/boot/Chainload.zig
+++ b/src/boot/Chainload.zig
@@ -4,40 +4,33 @@
 const Path = @import("../Config.zig").Path;
 const luefi = @import("../luefi.zig");
 
+const log = std.log.scoped(.chainload);
+
 const Chainload = @This();
-
-img_handle: uefi.Handle,
-img_loaded: *uefi.protocol.LoadedImage,
 
 img_buffer: []align(4096) u8,
 img_pages: usize,
 
 pub fn load(path: Path) !Chainload {
-    const buffer, const pages = blk: {
-        const file = try luefi.fileOpen(path);
-        defer file.close() catch unreachable;
-
-        break :blk try luefi.fileReadAlloc(file);
-    };
+    const buffer, const pages = try luefi.pathReadAlloc(path);
     errdefer luefi.freePages(buffer, pages);
 
-    const handle, const loaded = try luefi.loadImage(buffer);
-
     return .{
         .img_buffer = buffer,
         .img_pages = pages,
-        .img_handle = handle,
-        .img_loaded = loaded,
     };
 }
 
 pub fn deinit(self: Chainload) void {
-    luefi.unloadImage(self.img_handle);
     luefi.freePages(self.img_buffer, self.img_pages);
 }
 
 pub fn start(self: Chainload) !uefi.Status {
     const boot_svc = uefi.system_table.boot_services.?;
-    const exit_data = try boot_svc.startImage(self.img_handle);
+
+    const handle, _ = try luefi.loadImage(self.img_buffer);
+    defer luefi.unloadImage(handle);
+
+    const exit_data = try boot_svc.startImage(handle);
     return exit_data.code;
 }

src/boot/linux.zig modified +1 -1

diff --git a/src/boot/linux.zig b/src/boot/linux.zig
index c285e0caf611c42048b90c3ed5aa35084232f5aa..7f83d9995b046ddb04d1fdef2f1be04d891eca19 100644
--- a/src/boot/linux.zig
+++ b/src/boot/linux.zig
@@ -1,2 +1,2 @@
-pub const Kernel = @import("./linux/Kernel.zig");
 pub const Initrd = @import("./linux/Initrd.zig");
+pub const Kernel = @import("./linux/Kernel.zig");

src/boot/linux/Initrd.zig modified +6 -2

diff --git a/src/boot/linux/Initrd.zig b/src/boot/linux/Initrd.zig
index f2e0fc75d1b06682fae29e34036d9d9f20e55a8a..00e791528a3b2aba78e6a1072aac43a38a09577a 100644
--- a/src/boot/linux/Initrd.zig
+++ b/src/boot/linux/Initrd.zig
@@ -2,8 +2,8 @@ const std = @import("std");
 const uefi = std.os.uefi;
 const log = std.log;
 
-const luefi = @import("../../luefi.zig");
 const Path = @import("../../Config.zig").Path;
+const luefi = @import("../../luefi.zig");
 
 const Initrd = @This();
 
@@ -11,6 +11,8 @@ protocol: LoadFile2 = .{},
 
 initrd_buf: []align(4096) u8,
 initrd_pages: usize,
+
+handle: uefi.Handle,
 
 pub fn load(paths: []const Path) !*Initrd {
     const File = struct { file: *uefi.protocol.File, size: usize };
@@ -54,14 +56,16 @@
     self.* = .{
         .initrd_buf = initrd_buf,
         .initrd_pages = pages_count,
+        .handle = undefined,
     };
 
-    try luefi.installProtocols(.{ &device_path, &self.protocol });
+    self.handle = try luefi.installProtocols(.{ &device_path, &self.protocol });
 
     return self;
 }
 
 pub fn deinit(self: *Initrd) void {
+    luefi.uninstallProtocols(self.handle, .{ &device_path, &self.protocol });
     luefi.freePages(self.initrd_buf, self.initrd_pages);
     uefi.pool_allocator.destroy(self);
 }

src/boot/linux/Kernel.zig modified +26 -11

diff --git a/src/boot/linux/Kernel.zig b/src/boot/linux/Kernel.zig
index 5401c04123dbd3cb37b6b8151e1c1bcd28be4184..aa81937646b142858473612e5447d953d530b419 100644
--- a/src/boot/linux/Kernel.zig
+++ b/src/boot/linux/Kernel.zig
@@ -1,33 +1,48 @@
 const std = @import("std");
 const uefi = std.os.uefi;
-const log = std.log;
+const builtin = @import("builtin");
 
+const Path = @import("../../Config.zig").Path;
+const luefi = @import("../../luefi.zig");
 const Chainload = @import("../Chainload.zig");
 
-const luefi = @import("../../luefi.zig");
-const Path = @import("../../Config.zig").Path;
+const log = std.log.scoped(.linux);
 
 const Kernel = @This();
 
-chainload: Chainload,
+img_buffer: []align(4096) u8,
+img_pages: usize,
 
 pub fn load(path: Path) !Kernel {
     log.info("loading kernel {f}", .{path});
-    return .{ .chainload = try .load(path) };
+
+    const buffer, const pages = try luefi.pathReadAlloc(path);
+    return .{ .img_buffer = buffer, .img_pages = pages };
 }
 
 pub fn deinit(self: Kernel) void {
-    self.chainload.deinit();
+    luefi.freePages(self.img_buffer, self.img_pages);
 }
 
 pub fn start(self: Kernel, cmdline: []const u8) !uefi.Status {
     const allocator = uefi.pool_allocator;
+    const boot_svc = uefi.system_table.boot_services.?;
 
-    const cmdline_utf16 = try std.unicode.utf8ToUtf16LeAllocZ(allocator, cmdline);
-    defer allocator.free(cmdline_utf16);
+    if (builtin.target.cpu.arch == .x86_64) {
+        const cmdline_utf16 = try std.unicode.utf8ToUtf16LeAllocZ(allocator, cmdline);
+        defer allocator.free(cmdline_utf16);
 
-    self.chainload.img_loaded.load_options = @ptrCast(cmdline_utf16);
-    self.chainload.img_loaded.load_options_size = @intCast(cmdline_utf16.len * @sizeOf(u16));
+        const handle, const loaded = try luefi.loadImage(self.img_buffer);
+        defer luefi.unloadImage(handle);
 
-    return self.chainload.start();
+        loaded.load_options = @ptrCast(cmdline_utf16);
+        loaded.load_options_size = @intCast(cmdline_utf16.len * @sizeOf(u16));
+
+        const exit_data = try boot_svc.startImage(handle);
+        try exit_data.code.err();
+        return .aborted;
+    } else {
+        log.err("this architecture doesnt support linux boot", .{});
+        return error.UnsupportedArchitecture;
+    }
 }

src/luefi.zig modified +35 -10

diff --git a/src/luefi.zig b/src/luefi.zig
index 9f3640b3c8d19454e2b9e087b60ad2a7f2ffdd1d..3158d9994bd03d6db8b49976ddb0a9097dd124a0 100644
--- a/src/luefi.zig
+++ b/src/luefi.zig
@@ -87,6 +87,13 @@     const slice_ptr: [*]align(4096) [4096]u8 = @ptrCast(buf);
     boot_svc.freePages(slice_ptr[0..page_count]) catch unreachable;
 }
 
+pub fn pathReadAlloc(path: Path) !struct { []align(4096) u8, usize } {
+    const file = try fileOpen(path);
+    defer file.close() catch unreachable;
+
+    return fileReadAlloc(file);
+}
+
 pub fn loadImage(buf: []u8) !struct { uefi.Handle, *uefi.protocol.LoadedImage } {
     const boot_svc = uefi.system_table.boot_services.?;
 
@@ -105,35 +112,53 @@     const boot_svc = uefi.system_table.boot_services.?;
     _ = boot_svc.unloadImage(handle) catch unreachable;
 }
 
-// https://codeberg.org/ziglang/zig/pulls/31934
-pub fn installProtocols(interfaces: anytype) !void {
-    const fields = @typeInfo(@TypeOf(interfaces)).@"struct".fields;
-    comptime var tuple_types: [fields.len * 2 + 1]type = undefined;
+fn ProtocolInterfaceArgs(Handle: type, Interfaces: type) type {
+    const fields = @typeInfo(Interfaces).@"struct".fields;
+    comptime var tuple_types: [fields.len * 2 + 2]type = undefined;
     inline for (0..fields.len) |i| {
         tuple_types[i * 2 + 1] = *const uefi.Guid;
         tuple_types[i * 2 + 2] = *const anyopaque;
     }
-    tuple_types[0] = *?uefi.Handle;
+    tuple_types[0] = Handle;
+    tuple_types[tuple_types.len - 1] = ?*anyopaque;
+    return @Tuple(tuple_types[0..]);
+}
 
-    var args_tuple: @Tuple(tuple_types[0..]) = undefined;
+fn protocolInterfaceArgs(handle: anytype, interfaces: anytype) ProtocolInterfaceArgs(@TypeOf(handle), @TypeOf(interfaces)) {
+    var args_tuple: ProtocolInterfaceArgs(@TypeOf(handle), @TypeOf(interfaces)) = undefined;
     inline for (interfaces, 0..) |iface, i| {
         const child = @typeInfo(@TypeOf(iface)).pointer.child;
         args_tuple[i * 2 + 1] = &@field(child, "guid");
         args_tuple[i * 2 + 2] = @ptrCast(iface);
     }
-    var handle: ?uefi.Handle = null;
-    args_tuple[0] = &handle;
+    args_tuple[0] = handle;
+    args_tuple[@typeInfo(@TypeOf(args_tuple)).@"struct".fields.len - 1] = @as(?*anyopaque, null);
+    return args_tuple;
+}
 
+// https://codeberg.org/ziglang/zig/pulls/31934
+pub fn installProtocols(interfaces: anytype) !uefi.Handle {
     const boot_svc = uefi.system_table.boot_services.?;
     const InstallFn = *const fn (handle: *?uefi.Handle, ...) callconv(uefi.cc) uefi.Status;
     const installFn: InstallFn = @ptrCast(boot_svc._installMultipleProtocolInterfaces);
 
-    const status = @call(.auto, installFn, args_tuple);
+    var hdl: ?uefi.Handle = null;
+
+    const status = @call(.auto, installFn, protocolInterfaceArgs(&hdl, interfaces));
     return switch (status) {
-        .success => return,
+        .success => return hdl.?,
         .already_started => return error.AlreadyStarted,
         .out_of_resources => return error.OutOfResources,
         .invalid_parameter => return error.InvalidParameter,
         else => return uefi.unexpectedStatus(status),
     };
 }
+
+pub fn uninstallProtocols(handle: uefi.Handle, interfaces: anytype) void {
+    const boot_svc = uefi.system_table.boot_services.?;
+    const UninstallFn = *const fn (handle: uefi.Handle, ...) callconv(uefi.cc) uefi.Status;
+    const uninstallFn: UninstallFn = @ptrCast(boot_svc._uninstallMultipleProtocolInterfaces);
+
+    const status = @call(.auto, uninstallFn, protocolInterfaceArgs(handle, interfaces));
+    if (status != .success) unreachable;
+}

src/main.zig modified +1 -1

diff --git a/src/main.zig b/src/main.zig
index d736ad92c5ca902cae0f1d35f6aa989d1ecfd67d..6a39a35035156c53c1be9a612f00ce9b008d9ff3 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -62,7 +62,7 @@ }
 
 pub fn main() uefi.Status {
     return bmain() catch |err| {
-        log.err("unrecoverable error in bmain: {}", .{err});
+        log.err("unrecoverable error: {}", .{err});
         return .aborted;
     };
 }