lumine
UEFI x86_64 bootloader for linux kernel images
commit 1a28f8f0
| author | dragsbruh <dragsbruh@protonmail.com> |
|---|---|
| date | 2026-07-22 16:56:28 +0530 |
| committer | dragsbruh <dragsbruh@protonmail.com> (2026-07-22 16:56:28 +0530) |
| tree | 78b8d30446dd8a4ec9cdb1f6b70a1afd6b891d0e |
| parent | 88026b83 |
feat: chainload another efi
+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(