//! lumine uefi abstractions const std = @import("std"); const uefi = std.os.uefi; const Path = @import("./Config.zig").Path; pub fn posixPathToUefi(path: []u16) void { for (0..path.len) |i| switch (path[i]) { '/' => path[i] = '\\', else => {}, }; } pub fn fileOpenBoot(path: []const u8) !*uefi.protocol.File { const boot_svc = uefi.system_table.boot_services.?; const allocator = uefi.pool_allocator; const loaded_img = try boot_svc.openProtocol(uefi.protocol.LoadedImage, uefi.handle, .{ .get_protocol = .{ .agent = uefi.handle }, }) orelse unreachable; const device_handle = loaded_img.device_handle orelse unreachable; const simple_fs = try boot_svc.openProtocol(uefi.protocol.SimpleFileSystem, device_handle, .{ .get_protocol = .{ .agent = uefi.handle }, }) orelse unreachable; const root_fs = try simple_fs.openVolume(); defer root_fs.close() catch unreachable; // is allocating a good idea? const utf16_path = try std.unicode.utf8ToUtf16LeAllocZ(allocator, path); defer allocator.free(utf16_path); posixPathToUefi(utf16_path); return root_fs.open(utf16_path, .read, .{}); } pub fn fileOpen(path: Path) !*uefi.protocol.File { return switch (path) { .boot => |boot_path| fileOpenBoot(boot_path), }; } pub fn fileSize(file: *uefi.protocol.File) !usize { const allocator = uefi.pool_allocator; const info_size = try file.getInfoSize(.file); const info_buf = try allocator.alignedAlloc(u8, .@"8", info_size); defer allocator.free(info_buf); const info = try file.getInfo(.file, info_buf); return info.file_size; } pub fn fileReadSlice(file: *uefi.protocol.File, slice: []u8) !void { var read: usize = 0; while (read < slice.len) { const chunk = try file.read(slice[read..]); 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_ptr: [*]align(4096) u8 = @ptrCast(pages); const slice = slice_ptr[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 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.?; 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; } 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] = Handle; tuple_types[tuple_types.len - 1] = ?*anyopaque; return @Tuple(tuple_types[0..]); } 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); } 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); var hdl: ?uefi.Handle = null; const status = @call(.auto, installFn, protocolInterfaceArgs(&hdl, interfaces)); return switch (status) { .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; }