λ

lumine

UEFI x86_64 bootloader for linux kernel images

settings

diff

88026b83 to 1a28f8f0

+140 -78

build.zig modified +12 -8

diff --git a/build.zig b/build.zig
index c5de48379c847934e040c7dcc1e797a6f2739879..5068f3d8d4cab96c2f833906101a8afa4d16f0b0 100644
--- a/build.zig
+++ b/build.zig
@@ -59,7 +59,12 @@         all_step.dependOn(&arch_install.step);
     }
 
     const qemu_exe = b.option([]const u8, "qemu", "ovveride qemu executable") orelse b.fmt("qemu-system-{t}", .{cpu_arch});
-    const qemu_cmd = b.addSystemCommand(&.{ qemu_exe, "-m", "512M", "-no-reboot", "-no-shutdown", "-serial", "mon:stdio", "-nographic" });
+    const qemu_cmd = b.addSystemCommand(&.{ qemu_exe, "-m", "512M", "-no-reboot", "-no-shutdown" });
+
+    const no_serial = b.option(bool, "no-serial", "dont add stdio serial commands") orelse false;
+    if (!no_serial) {
+        qemu_cmd.addArgs(&.{ "-serial", "mon:stdio", "-nographic" });
+    }
 
     switch (cpu_arch) {
         .aarch64 => qemu_cmd.addArgs(&.{ "-M", "virt", "-cpu", "cortex-a57" }),
@@ -85,16 +90,15 @@         qemu_cmd.step.dependOn(&fail.step);
     }
 
     {
+        // https://codeberg.org/ziglang/zig/issues/32041
+        // https://codeberg.org/dragsbruh/minimux/releases
+        const linux_dir = b.option([]const u8, "linux", "dir containing `vmlinuz` and `initrd.xz`") orelse b.fmt("res/{t}", .{cpu_arch});
+        const config_file = b.option([]const u8, "config", "path to config file") orelse "res/lumine.zon";
+
         const boot_dir = b.addTempFiles();
-        _ = boot_dir.addCopyFile(lumine_exe.getEmittedBin(), b.fmt("EFI/BOOT/BOOT{t}.EFI", .{cpu_arch.efi()}));
-
-        const config_file = b.option([]const u8, "config", "path to config file") orelse "res/lumine.zon";
 
         _ = boot_dir.addCopyFile(b.path(config_file), "lumine.zon");
-
-        // https://codeberg.org/ziglang/zig/issues/32041
-        // https://codeberg.org/dragsbruh/minimux/releases
-        const linux_dir = b.option([]const u8, "linux", "dir containing `vmlinuz` and `initrd.xz`") orelse b.fmt("res/{t}", .{cpu_arch});
+        _ = boot_dir.addCopyFile(lumine_exe.getEmittedBin(), b.fmt("EFI/BOOT/BOOT{t}.EFI", .{cpu_arch.efi()}));
 
         _ = boot_dir.addCopyFile(b.path(linux_dir).path(b, "vmlinuz"), "linux/vmlinuz");
         _ = boot_dir.addCopyFile(b.path(linux_dir).path(b, "initrd.xz"), "linux/initrd.xz");

src/Config.zig modified +1 -0

diff --git a/src/Config.zig b/src/Config.zig
index 39d11ee4531fb6b62c43b850b1484e8259350fae..88be0a33fcb0bcf7bed603fbeb95d213d87c089c 100644
--- a/src/Config.zig
+++ b/src/Config.zig
@@ -14,6 +14,7 @@     }
 };
 
 pub const BootOption = union(enum) {
+    chainload: Path,
     linux: struct {
         kernel_path: Path,
         kernel_cmdline: []const u8 = &.{},

src/boot/Chainload.zig added +43 -0

diff --git a/src/boot/Chainload.zig b/src/boot/Chainload.zig
new file mode 100644
index 0000000000000000000000000000000000000000..c205dcef3420483308c42d99f720284585815c92
--- /dev/null
+++ b/src/boot/Chainload.zig
@@ -0,0 +1,43 @@
+const std = @import("std");
+const uefi = std.os.uefi;
+
+const Path = @import("../Config.zig").Path;
+const luefi = @import("../luefi.zig");
+
+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);
+    };
+    defer 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);
+    return exit_data.code;
+}

src/boot/linux/Kernel.zig added +31 -0

diff --git a/src/boot/linux/Kernel.zig b/src/boot/linux/Kernel.zig
new file mode 100644
index 0000000000000000000000000000000000000000..3b54c20e1c0099005e5039119e43dabcdf91b252
--- /dev/null
+++ b/src/boot/linux/Kernel.zig
@@ -0,0 +1,31 @@
+const std = @import("std");
+const uefi = std.os.uefi;
+
+const Chainload = @import("../Chainload.zig");
+
+const luefi = @import("../../luefi.zig");
+const Path = @import("../../Config.zig").Path;
+
+const Kernel = @This();
+
+chainload: Chainload,
+
+pub fn load(path: Path) !Kernel {
+    return .{ .chainload = try .load(path) };
+}
+
+pub fn deinit(self: Kernel) void {
+    self.chainload.deinit();
+}
+
+pub fn start(self: Kernel, cmdline: []const u8) !uefi.Status {
+    const allocator = uefi.pool_allocator;
+
+    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));
+
+    return self.chainload.start();
+}

src/linux.zig => src/boot/linux.zig renamed +0 -0

diff --git a/src/linux.zig b/src/boot/linux.zig
rename from src/linux.zig
rename to src/boot/linux.zig

src/linux/Kernel.zig deleted +0 -63

diff --git a/src/linux/Kernel.zig b/src/linux/Kernel.zig
deleted file mode 100644
index 8fd8289e9b1a6ee6df727940ba9b8e94f61fce87..0000000000000000000000000000000000000000
--- a/src/linux/Kernel.zig
+++ /dev/null
@@ -1,63 +0,0 @@
-const std = @import("std");
-const uefi = std.os.uefi;
-
-const luefi = @import("../luefi.zig");
-const Path = @import("../Config.zig").Path;
-
-const log = std.log.scoped(.linux);
-
-const Kernel = @This();
-
-kernel_pages: usize,
-kernel_buf: []align(4096) u8,
-
-pub fn load(path: Path) !Kernel {
-    const boot_svc = uefi.system_table.boot_services.?;
-
-    log.info("loading kernel from {f}", .{path});
-    const file = try luefi.fileOpen(path);
-    defer file.close() catch unreachable;
-
-    const size = try luefi.fileSize(file);
-    const page_count = try std.math.divCeil(usize, size, 4096);
-
-    const pages = try boot_svc.allocatePages(.any, .loader_data, page_count);
-    errdefer boot_svc.freePages(pages) catch unreachable;
-
-    const slice = @as([*]align(4096) u8, @ptrCast(pages))[0..size];
-    try luefi.fileReadSlice(file, slice);
-
-    return .{
-        .kernel_buf = slice,
-        .kernel_pages = size,
-    };
-}
-
-pub fn deinit(self: Kernel) void {
-    const boot_svc = uefi.system_table.boot_services.?;
-    const pages = @as([*]align(4096) [4096]u8, @ptrCast(self.kernel_buf));
-    boot_svc.freePages(pages[0..self.kernel_pages]) catch unreachable;
-}
-
-pub fn start(self: Kernel, cmdline: []const u8) !void {
-    const boot_svc = uefi.system_table.boot_services.?;
-    const allocator = uefi.pool_allocator;
-
-    const cmdline_utf16 = try std.unicode.utf8ToUtf16LeAllocZ(allocator, cmdline);
-    defer allocator.free(cmdline_utf16);
-
-    const img_handle = try boot_svc.loadImage(false, uefi.handle, .{ .buffer = self.kernel_buf });
-    defer _ = boot_svc.unloadImage(img_handle) catch unreachable;
-
-    const loaded_img = try boot_svc.openProtocol(uefi.protocol.LoadedImage, img_handle, .{
-        .get_protocol = .{ .agent = uefi.handle },
-    }) orelse unreachable;
-
-    loaded_img.load_options = @ptrCast(cmdline_utf16);
-    loaded_img.load_options_size = @intCast(cmdline_utf16.len * @sizeOf(u16));
-
-    const exit_data = try boot_svc.startImage(img_handle);
-    log.err("kernel image exited with exit code {t}", .{exit_data.code});
-
-    return error.KernelExited;
-}

src/luefi.zig modified +40 -0

diff --git a/src/luefi.zig b/src/luefi.zig
index 0be53d5070e981bba11affc72cda848680711e1a..872ae2ccb6b49f1b36c54d52d4556db1c06ae45f 100644
--- a/src/luefi.zig
+++ b/src/luefi.zig
@@ -63,3 +63,43 @@         if (chunk == 0) return error.ReadShort;
         read += chunk;
     }
 }
+
+pub fn fileReadAlloc(file: *uefi.protocol.File) !struct { []align(4096) u8, usize } {
+    const boot_svc = uefi.system_table.boot_services.?;
+
+    const size = try fileSize(file);
+    const page_count = try std.math.divCeil(usize, size, 4096);
+
+    const pages = try boot_svc.allocatePages(.any, .loader_data, page_count);
+    errdefer boot_svc.freePages(pages) catch unreachable;
+
+    const slice: []align(4096) u8 = @as([*]align(4096) u8, @ptrCast(pages))[0..size];
+    try fileReadSlice(file, slice);
+
+    return .{ slice, page_count };
+}
+
+pub fn freePages(buf: []align(4096) u8, page_count: usize) void {
+    const boot_svc = uefi.system_table.boot_services.?;
+
+    const pages = @as([*]align(4096) [4096]u8, @ptrCast(buf))[0..page_count];
+    boot_svc.freePages(pages) catch unreachable;
+}
+
+pub fn loadImage(buf: []u8) !struct { uefi.Handle, *uefi.protocol.LoadedImage } {
+    const boot_svc = uefi.system_table.boot_services.?;
+
+    const handle = try boot_svc.loadImage(false, uefi.handle, .{ .buffer = buf });
+    errdefer unloadImage(handle);
+
+    const loaded_img = try boot_svc.openProtocol(uefi.protocol.LoadedImage, handle, .{
+        .get_protocol = .{ .agent = uefi.handle },
+    }) orelse unreachable;
+
+    return .{ handle, loaded_img };
+}
+
+pub fn unloadImage(handle: uefi.Handle) void {
+    const boot_svc = uefi.system_table.boot_services.?;
+    _ = boot_svc.unloadImage(handle) catch unreachable;
+}

src/main.zig modified +13 -7

diff --git a/src/main.zig b/src/main.zig
index 405011792adeb8f3714b0b3e80b12579cac5b977..fa44f0f86b608ad6ec12ee3f9f1222f6a51707be 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -3,9 +3,10 @@ const uefi = std.os.uefi;
 const log = std.log;
 const Io = std.Io;
 
+const Chainload = @import("./boot/Chainload.zig");
+const linux = @import("./boot/linux.zig");
 const Config = @import("./Config.zig");
 const Console = @import("./Console.zig");
-const linux = @import("./linux.zig");
 const luefi = @import("./luefi.zig");
 
 const config_paths: []const Config.Path = &.{
@@ -14,7 +15,7 @@     .{ .boot = "/lumine/lumine.zon" },
     .{ .boot = "/lumine/config.zon" },
 };
 
-pub fn bmain() !void {
+pub fn bmain() !uefi.Status {
     const allocator = uefi.pool_allocator;
 
     const config: Config = try config: {
@@ -39,23 +40,28 @@     };
     defer std.zon.parse.free(allocator, config);
 
     switch (config.boot) {
+        .chainload => |efi_path| {
+            log.info("loading efi {f}", .{efi_path});
+            const chainload: Chainload = try .load(efi_path);
+            defer chainload.deinit();
+
+            return chainload.start();
+        },
         .linux => |linux_conf| {
+            log.info("loading kernel {f}", .{linux_conf.kernel_path});
             const kernel: linux.Kernel = try .load(linux_conf.kernel_path);
             defer kernel.deinit();
 
-            try kernel.start(linux_conf.kernel_cmdline);
+            return kernel.start(linux_conf.kernel_cmdline);
         },
     }
 }
 
 pub fn main() uefi.Status {
-    bmain() catch |err| {
+    return bmain() catch |err| {
         log.err("unrecoverable error in bmain: {}", .{err});
         return .aborted;
     };
-
-    log.err("bootloader exited successfully, this shouldnt happen", .{});
-    while (true) std.atomic.spinLoopHint();
 }
 
 pub fn logFn(