lumine
UEFI x86_64 bootloader for linux kernel images
src/luefi.zig
5828 bytes raw
1//! lumine uefi abstractions
2const std = @import("std");
3const uefi = std.os.uefi;
4
5const Path = @import("./Config.zig").Path;
6
7pub fn posixPathToUefi(path: []u16) void {
8 for (0..path.len) |i|
9 switch (path[i]) {
10 '/' => path[i] = '\\',
11 else => {},
12 };
13}
14
15pub fn fileOpenBoot(path: []const u8) !*uefi.protocol.File {
16 const boot_svc = uefi.system_table.boot_services.?;
17 const allocator = uefi.pool_allocator;
18
19 const loaded_img = try boot_svc.openProtocol(uefi.protocol.LoadedImage, uefi.handle, .{
20 .get_protocol = .{ .agent = uefi.handle },
21 }) orelse unreachable;
22
23 const device_handle = loaded_img.device_handle orelse unreachable;
24 const simple_fs = try boot_svc.openProtocol(uefi.protocol.SimpleFileSystem, device_handle, .{
25 .get_protocol = .{ .agent = uefi.handle },
26 }) orelse unreachable;
27
28 const root_fs = try simple_fs.openVolume();
29 defer root_fs.close() catch unreachable;
30
31 // is allocating a good idea?
32 const utf16_path = try std.unicode.utf8ToUtf16LeAllocZ(allocator, path);
33 defer allocator.free(utf16_path);
34
35 posixPathToUefi(utf16_path);
36
37 return root_fs.open(utf16_path, .read, .{});
38}
39
40pub fn fileOpen(path: Path) !*uefi.protocol.File {
41 return switch (path) {
42 .boot => |boot_path| fileOpenBoot(boot_path),
43 };
44}
45
46pub fn fileSize(file: *uefi.protocol.File) !usize {
47 const allocator = uefi.pool_allocator;
48
49 const info_size = try file.getInfoSize(.file);
50 const info_buf = try allocator.alignedAlloc(u8, .@"8", info_size);
51 defer allocator.free(info_buf);
52
53 const info = try file.getInfo(.file, info_buf);
54 return info.file_size;
55}
56
57pub fn fileReadSlice(file: *uefi.protocol.File, slice: []u8) !void {
58 var read: usize = 0;
59
60 while (read < slice.len) {
61 const chunk = try file.read(slice[read..]);
62 if (chunk == 0) return error.ReadShort;
63 read += chunk;
64 }
65}
66
67pub fn fileReadAlloc(file: *uefi.protocol.File) !struct { []align(4096) u8, usize } {
68 const boot_svc = uefi.system_table.boot_services.?;
69
70 const size = try fileSize(file);
71 const page_count = try std.math.divCeil(usize, size, 4096);
72
73 const pages = try boot_svc.allocatePages(.any, .loader_data, page_count);
74 errdefer boot_svc.freePages(pages) catch unreachable;
75
76 const slice_ptr: [*]align(4096) u8 = @ptrCast(pages);
77 const slice = slice_ptr[0..size];
78 try fileReadSlice(file, slice);
79
80 return .{ slice, page_count };
81}
82
83pub fn freePages(buf: []align(4096) u8, page_count: usize) void {
84 const boot_svc = uefi.system_table.boot_services.?;
85
86 const slice_ptr: [*]align(4096) [4096]u8 = @ptrCast(buf);
87 boot_svc.freePages(slice_ptr[0..page_count]) catch unreachable;
88}
89
90pub fn pathReadAlloc(path: Path) !struct { []align(4096) u8, usize } {
91 const file = try fileOpen(path);
92 defer file.close() catch unreachable;
93
94 return fileReadAlloc(file);
95}
96
97pub fn loadImage(buf: []u8) !struct { uefi.Handle, *uefi.protocol.LoadedImage } {
98 const boot_svc = uefi.system_table.boot_services.?;
99
100 const handle = try boot_svc.loadImage(false, uefi.handle, .{ .buffer = buf });
101 errdefer unloadImage(handle);
102
103 const loaded_img = try boot_svc.openProtocol(uefi.protocol.LoadedImage, handle, .{
104 .get_protocol = .{ .agent = uefi.handle },
105 }) orelse unreachable;
106
107 return .{ handle, loaded_img };
108}
109
110pub fn unloadImage(handle: uefi.Handle) void {
111 const boot_svc = uefi.system_table.boot_services.?;
112 _ = boot_svc.unloadImage(handle) catch unreachable;
113}
114
115fn ProtocolInterfaceArgs(Handle: type, Interfaces: type) type {
116 const fields = @typeInfo(Interfaces).@"struct".fields;
117 comptime var tuple_types: [fields.len * 2 + 2]type = undefined;
118 inline for (0..fields.len) |i| {
119 tuple_types[i * 2 + 1] = *const uefi.Guid;
120 tuple_types[i * 2 + 2] = *const anyopaque;
121 }
122 tuple_types[0] = Handle;
123 tuple_types[tuple_types.len - 1] = ?*anyopaque;
124 return @Tuple(tuple_types[0..]);
125}
126
127fn protocolInterfaceArgs(handle: anytype, interfaces: anytype) ProtocolInterfaceArgs(@TypeOf(handle), @TypeOf(interfaces)) {
128 var args_tuple: ProtocolInterfaceArgs(@TypeOf(handle), @TypeOf(interfaces)) = undefined;
129 inline for (interfaces, 0..) |iface, i| {
130 const child = @typeInfo(@TypeOf(iface)).pointer.child;
131 args_tuple[i * 2 + 1] = &@field(child, "guid");
132 args_tuple[i * 2 + 2] = @ptrCast(iface);
133 }
134 args_tuple[0] = handle;
135 args_tuple[@typeInfo(@TypeOf(args_tuple)).@"struct".fields.len - 1] = @as(?*anyopaque, null);
136 return args_tuple;
137}
138
139// https://codeberg.org/ziglang/zig/pulls/31934
140pub fn installProtocols(interfaces: anytype) !uefi.Handle {
141 const boot_svc = uefi.system_table.boot_services.?;
142 const InstallFn = *const fn (handle: *?uefi.Handle, ...) callconv(uefi.cc) uefi.Status;
143 const installFn: InstallFn = @ptrCast(boot_svc._installMultipleProtocolInterfaces);
144
145 var hdl: ?uefi.Handle = null;
146
147 const status = @call(.auto, installFn, protocolInterfaceArgs(&hdl, interfaces));
148 return switch (status) {
149 .success => return hdl.?,
150 .already_started => return error.AlreadyStarted,
151 .out_of_resources => return error.OutOfResources,
152 .invalid_parameter => return error.InvalidParameter,
153 else => return uefi.unexpectedStatus(status),
154 };
155}
156
157pub fn uninstallProtocols(handle: uefi.Handle, interfaces: anytype) void {
158 const boot_svc = uefi.system_table.boot_services.?;
159 const UninstallFn = *const fn (handle: uefi.Handle, ...) callconv(uefi.cc) uefi.Status;
160 const uninstallFn: UninstallFn = @ptrCast(boot_svc._uninstallMultipleProtocolInterfaces);
161
162 const status = @call(.auto, uninstallFn, protocolInterfaceArgs(handle, interfaces));
163 if (status != .success) unreachable;
164}