λ

lumine

UEFI x86_64 bootloader for linux kernel images

settings

src/luefi.zig

4730 bytes raw

//! 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 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;
}

// 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;
    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;

    var args_tuple: @Tuple(tuple_types[0..]) = 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;

    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);
    return switch (status) {
        .success => return,
        .already_started => return error.AlreadyStarted,
        .out_of_resources => return error.OutOfResources,
        .invalid_parameter => return error.InvalidParameter,
        else => return uefi.unexpectedStatus(status),
    };
}