booting into the kernel
This commit is contained in:
parent
3c8656fb25
commit
ca3c3d131c
53
build.zig
53
build.zig
|
|
@ -4,22 +4,46 @@ pub fn build(b: *std.Build) void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
// Shared handoff definitions (BootInfo, Framebuffer, ...). No target is set,
|
||||||
|
// so the module inherits the target of whichever binary imports it — the
|
||||||
|
// freestanding kernel or the UEFI bootloader.
|
||||||
const mod = b.addModule("danos", .{
|
const mod = b.addModule("danos", .{
|
||||||
.root_source_file = b.path("src/root.zig"),
|
.root_source_file = b.path("src/root.zig"),
|
||||||
.target = target,
|
});
|
||||||
|
|
||||||
|
// --- Kernel: freestanding x86_64 ELF, jumped to by the bootloader ---
|
||||||
|
// SSE2 is part of the x86_64 baseline and UEFI leaves it enabled at handoff,
|
||||||
|
// so we keep it: disabling it forces soft-float and makes the compiler unable
|
||||||
|
// to encode the vector ops that std's formatting/runtime still emit.
|
||||||
|
const kernel_target = b.resolveTargetQuery(.{
|
||||||
|
.cpu_arch = .x86_64,
|
||||||
|
.os_tag = .freestanding,
|
||||||
|
.abi = .none,
|
||||||
});
|
});
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "danos",
|
.name = "danos",
|
||||||
.root_module = b.createModule(.{
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
.target = target,
|
.target = kernel_target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.code_model = .small, // kernel is linked in the low 2 GiB (see image_base)
|
||||||
|
.red_zone = false, // interrupts would corrupt the SysV red zone
|
||||||
|
.single_threaded = true, // no scheduler yet; avoids pulling in TLS/atomics
|
||||||
|
.sanitize_c = .off, // the UBSan runtime needs f128/SSE support we don't provide
|
||||||
|
.stack_check = false, // stack-probe calls have no runtime to land in
|
||||||
|
.stack_protector = false,
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{ .name = "danos", .module = mod },
|
.{ .name = "danos", .module = mod },
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
exe.setLinkerScript(b.path("src/linker.ld"));
|
||||||
|
exe.entry = .{ .symbol_name = "_start" };
|
||||||
|
// Physical address the bootloader loads the kernel to (identity-mapped under
|
||||||
|
// UEFI). Overrides Zig's default image base so the linker script's layout is
|
||||||
|
// honoured; adjust here if it collides with firmware-reserved memory.
|
||||||
|
exe.image_base = 0x100000; // 1 MiB
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
|
@ -32,6 +56,9 @@ pub fn build(b: *std.Build) void {
|
||||||
.os_tag = .uefi,
|
.os_tag = .uefi,
|
||||||
}),
|
}),
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "danos", .module = mod },
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -55,6 +82,11 @@ pub fn build(b: *std.Build) void {
|
||||||
const efi_install = b.addInstallArtifact(efiexe, .{
|
const efi_install = b.addInstallArtifact(efiexe, .{
|
||||||
.dest_dir = .{ .override = .{ .custom = "esp/EFI/BOOT" } },
|
.dest_dir = .{ .override = .{ .custom = "esp/EFI/BOOT" } },
|
||||||
});
|
});
|
||||||
|
// The bootloader loads the kernel by name from the volume root, so drop the
|
||||||
|
// kernel ELF at esp/danos.
|
||||||
|
const kernel_install = b.addInstallArtifact(exe, .{
|
||||||
|
.dest_dir = .{ .override = .{ .custom = "esp" } },
|
||||||
|
});
|
||||||
|
|
||||||
// The firmware needs to write NVRAM, so give it a writable copy of the vars.
|
// The firmware needs to write NVRAM, so give it a writable copy of the vars.
|
||||||
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars });
|
const vars_copy = b.addSystemCommand(&.{ "cp", "-f", ovmf_vars });
|
||||||
|
|
@ -79,6 +111,7 @@ pub fn build(b: *std.Build) void {
|
||||||
"none",
|
"none",
|
||||||
});
|
});
|
||||||
run_efi.step.dependOn(&efi_install.step);
|
run_efi.step.dependOn(&efi_install.step);
|
||||||
|
run_efi.step.dependOn(&kernel_install.step);
|
||||||
|
|
||||||
const run_efi_step = b.step("run-efi", "Boot the EFI app in QEMU (OVMF)");
|
const run_efi_step = b.step("run-efi", "Boot the EFI app in QEMU (OVMF)");
|
||||||
run_efi_step.dependOn(&run_efi.step);
|
run_efi_step.dependOn(&run_efi.step);
|
||||||
|
|
@ -92,19 +125,19 @@ pub fn build(b: *std.Build) void {
|
||||||
// run_cmd.addArgs(args);
|
// run_cmd.addArgs(args);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// Tests run on the host. The kernel and bootloader target freestanding/UEFI
|
||||||
|
// and can't be executed natively, so only the shared module is unit-tested
|
||||||
|
// here (compiled for the host rather than inheriting a freestanding target).
|
||||||
const mod_tests = b.addTest(.{
|
const mod_tests = b.addTest(.{
|
||||||
.root_module = mod,
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
|
||||||
const exe_tests = b.addTest(.{
|
|
||||||
.root_module = exe.root_module,
|
|
||||||
});
|
|
||||||
|
|
||||||
const run_exe_tests = b.addRunArtifact(exe_tests);
|
|
||||||
|
|
||||||
const test_step = b.step("test", "Run tests");
|
const test_step = b.step("test", "Run tests");
|
||||||
test_step.dependOn(&run_mod_tests.step);
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
test_step.dependOn(&run_exe_tests.step);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
182
src/efi.zig
182
src/efi.zig
|
|
@ -1,15 +1,177 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const uefi = std.os.uefi;
|
const uefi = std.os.uefi;
|
||||||
|
const elf = std.elf;
|
||||||
|
const danos = @import("danos");
|
||||||
|
const BootInfo = danos.BootInfo;
|
||||||
|
|
||||||
|
/// Name of the kernel ELF on the boot volume (installed to the ESP root by
|
||||||
|
/// build.zig). UEFI wants a UTF-16, null-terminated path.
|
||||||
|
const kernel_file_name = std.unicode.utf8ToUtf16LeStringLiteral("danos");
|
||||||
|
|
||||||
|
/// Physical page size, and the sentinel UEFI uses to seek to end-of-file.
|
||||||
|
const page_size = 4096;
|
||||||
|
const seek_end = 0xffff_ffff_ffff_ffff;
|
||||||
|
|
||||||
pub fn main() uefi.Status {
|
pub fn main() uefi.Status {
|
||||||
// std's start code populates these globals before calling main.
|
// `boot` never returns on success — it jumps into the kernel. If it fails,
|
||||||
const con_out = uefi.system_table.con_out orelse return .device_error;
|
// report the reason (boot services are still up) and park the machine so the
|
||||||
|
// message stays on screen.
|
||||||
// UEFI console output expects a null-terminated UTF-16 string.
|
boot() catch |err| {
|
||||||
const message = std.unicode.utf8ToUtf16LeStringLiteral("Hello from Zig UEFI!\r\n");
|
log("\r\ndanos: boot failed: ");
|
||||||
_ = con_out.outputString(message) catch {};
|
logBytes(@errorName(err));
|
||||||
|
log("\r\n");
|
||||||
while (true) {}
|
while (true) asm volatile ("hlt");
|
||||||
|
};
|
||||||
return .success;
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boot() !noreturn {
|
||||||
|
const bs = uefi.system_table.boot_services orelse return error.NoBootServices;
|
||||||
|
|
||||||
|
// Everything the kernel needs must be gathered *before* we exit boot
|
||||||
|
// services, since afterwards none of these calls are usable.
|
||||||
|
var boot_info: BootInfo = .{
|
||||||
|
.framebuffer = try queryFramebuffer(bs),
|
||||||
|
};
|
||||||
|
|
||||||
|
const entry = try loadKernel(bs);
|
||||||
|
|
||||||
|
log("danos: kernel loaded, exiting boot services\r\n");
|
||||||
|
try exitBootServices(bs);
|
||||||
|
|
||||||
|
// Hand control to the kernel. `danos.kernel_abi` is SysV, so the pointer is
|
||||||
|
// passed in RDI as the kernel expects — not RCX, which this UEFI binary's
|
||||||
|
// default `.c` convention (Microsoft x64) would use.
|
||||||
|
const kernel: *const fn (*const BootInfo) callconv(danos.kernel_abi) noreturn = @ptrFromInt(entry);
|
||||||
|
kernel(&boot_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the current graphics mode into our own framebuffer description.
|
||||||
|
fn queryFramebuffer(bs: *uefi.tables.BootServices) !danos.Framebuffer {
|
||||||
|
const gop = (try bs.locateProtocol(uefi.protocol.GraphicsOutput, null)) orelse
|
||||||
|
return error.NoGraphicsOutput;
|
||||||
|
const info = gop.mode.info;
|
||||||
|
return .{
|
||||||
|
.base = @intCast(gop.mode.frame_buffer_base),
|
||||||
|
.width = info.horizontal_resolution,
|
||||||
|
.height = info.vertical_resolution,
|
||||||
|
// Each pixel is 32 bits, so the byte pitch is 4 * pixels-per-row.
|
||||||
|
.pitch = info.pixels_per_scan_line * 4,
|
||||||
|
.format = switch (info.pixel_format) {
|
||||||
|
.red_green_blue_reserved_8_bit_per_color => .rgbx,
|
||||||
|
.blue_green_red_reserved_8_bit_per_color => .bgrx,
|
||||||
|
// bit_mask / blt_only have no linear 32bpp layout we can paint into.
|
||||||
|
else => return error.UnsupportedPixelFormat,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open the kernel on the volume we booted from, read it into a pool buffer,
|
||||||
|
/// load its segments, and return the physical entry-point address.
|
||||||
|
fn loadKernel(bs: *uefi.tables.BootServices) !usize {
|
||||||
|
const loaded = (try bs.handleProtocol(uefi.protocol.LoadedImage, uefi.handle)) orelse
|
||||||
|
return error.NoLoadedImage;
|
||||||
|
const device = loaded.device_handle orelse return error.NoBootDevice;
|
||||||
|
const fs = (try bs.handleProtocol(uefi.protocol.SimpleFileSystem, device)) orelse
|
||||||
|
return error.NoFileSystem;
|
||||||
|
|
||||||
|
const root = try fs.openVolume();
|
||||||
|
defer _ = root.close() catch {};
|
||||||
|
|
||||||
|
const file = try root.open(kernel_file_name, .read, .{});
|
||||||
|
defer _ = file.close() catch {};
|
||||||
|
|
||||||
|
// Seek to the end to learn the size, then rewind.
|
||||||
|
try file.setPosition(seek_end);
|
||||||
|
const size: usize = @intCast(try file.getPosition());
|
||||||
|
try file.setPosition(0);
|
||||||
|
|
||||||
|
const image = try bs.allocatePool(.loader_data, size);
|
||||||
|
defer _ = bs.freePool(image.ptr) catch {};
|
||||||
|
|
||||||
|
// `read` may return short; loop until the whole file is in memory.
|
||||||
|
var read_total: usize = 0;
|
||||||
|
while (read_total < size) {
|
||||||
|
const n = try file.read(image[read_total..]);
|
||||||
|
if (n == 0) return error.UnexpectedEof;
|
||||||
|
read_total += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadElf(bs, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate the ELF and copy every PT_LOAD segment to its physical address.
|
||||||
|
fn loadElf(bs: *uefi.tables.BootServices, image: []u8) !usize {
|
||||||
|
if (image.len < @sizeOf(elf.Elf64_Ehdr)) return error.NotElf;
|
||||||
|
const ehdr: *const elf.Elf64_Ehdr = @ptrCast(@alignCast(image.ptr));
|
||||||
|
|
||||||
|
if (ehdr.e_ident[0] != 0x7f or ehdr.e_ident[1] != 'E' or
|
||||||
|
ehdr.e_ident[2] != 'L' or ehdr.e_ident[3] != 'F') return error.NotElf;
|
||||||
|
if (ehdr.e_machine != .X86_64) return error.WrongArchitecture;
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < ehdr.e_phnum) : (i += 1) {
|
||||||
|
const phdr: *const elf.Elf64_Phdr = @ptrCast(@alignCast(
|
||||||
|
image.ptr + ehdr.e_phoff + i * ehdr.e_phentsize,
|
||||||
|
));
|
||||||
|
if (phdr.p_type != elf.PT_LOAD) continue;
|
||||||
|
|
||||||
|
// Reserve the exact physical pages this segment is linked at. This
|
||||||
|
// requires the segment's p_paddr to be free in the firmware memory map;
|
||||||
|
// if it collides, adjust `image_base` in build.zig.
|
||||||
|
const mem_sz: usize = @intCast(phdr.p_memsz);
|
||||||
|
const pages = (mem_sz + page_size - 1) / page_size;
|
||||||
|
const dest: [*]align(page_size) uefi.Page = @ptrFromInt(phdr.p_paddr);
|
||||||
|
_ = try bs.allocatePages(.{ .address = dest }, .loader_data, pages);
|
||||||
|
|
||||||
|
// Copy the file-backed part, then zero the .bss tail (memsz > filesz).
|
||||||
|
const bytes: [*]u8 = @ptrFromInt(phdr.p_paddr);
|
||||||
|
const file_sz: usize = @intCast(phdr.p_filesz);
|
||||||
|
const off: usize = @intCast(phdr.p_offset);
|
||||||
|
@memcpy(bytes[0..file_sz], image[off..][0..file_sz]);
|
||||||
|
@memset(bytes[file_sz..mem_sz], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return @intCast(ehdr.e_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch the memory map and exit boot services. Allocating the map buffer can
|
||||||
|
/// itself change the map (invalidating the key), so retry until it takes.
|
||||||
|
fn exitBootServices(bs: *uefi.tables.BootServices) !void {
|
||||||
|
var attempts: usize = 0;
|
||||||
|
while (attempts < 8) : (attempts += 1) {
|
||||||
|
const info = try bs.getMemoryMapInfo();
|
||||||
|
// Spare descriptors to absorb the growth from the allocatePool below.
|
||||||
|
const buf = try bs.allocatePool(.loader_data, (info.len + 8) * info.descriptor_size);
|
||||||
|
const map = bs.getMemoryMap(buf) catch {
|
||||||
|
_ = bs.freePool(buf.ptr) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
bs.exitBootServices(uefi.handle, map.info.key) catch {
|
||||||
|
_ = bs.freePool(buf.ptr) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
return; // Boot services are gone; do not touch `bs` again.
|
||||||
|
}
|
||||||
|
return error.ExitBootServicesFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a compile-time string to the console (best effort).
|
||||||
|
fn log(comptime msg: []const u8) void {
|
||||||
|
const out = uefi.system_table.con_out orelse return;
|
||||||
|
_ = out.outputString(std.unicode.utf8ToUtf16LeStringLiteral(msg)) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a runtime ASCII byte string (e.g. an @errorName) by widening to UTF-16.
|
||||||
|
fn logBytes(bytes: []const u8) void {
|
||||||
|
const out = uefi.system_table.con_out orelse return;
|
||||||
|
var buf: [128]u16 = undefined;
|
||||||
|
var i: usize = 0;
|
||||||
|
for (bytes) |b| {
|
||||||
|
if (i + 1 >= buf.len) break;
|
||||||
|
buf[i] = b;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
buf[i] = 0;
|
||||||
|
_ = out.outputString(buf[0..i :0].ptr) catch {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/* Kernel link layout.
|
||||||
|
*
|
||||||
|
* The kernel is linked at a fixed low physical address (set by `image_base` in
|
||||||
|
* build.zig). UEFI runs with memory identity-mapped, so the bootloader can load
|
||||||
|
* each PT_LOAD segment to the physical address matching its virtual address and
|
||||||
|
* jump straight to _start — no page tables to build yet. (Moving to a
|
||||||
|
* higher-half virtual base is a later step, once the bootloader sets up paging.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
/* One loadable segment per permission set, so the loader can map .text as R+X,
|
||||||
|
* .rodata as R, and .data/.bss as R+W. FLAGS bits: 1=X, 2=W, 4=R. */
|
||||||
|
PHDRS {
|
||||||
|
text PT_LOAD FLAGS(5); /* R + X */
|
||||||
|
rodata PT_LOAD FLAGS(4); /* R */
|
||||||
|
data PT_LOAD FLAGS(6); /* R + W */
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTIONS {
|
||||||
|
.text ALIGN(4K) : {
|
||||||
|
*(.text .text.*)
|
||||||
|
} :text
|
||||||
|
|
||||||
|
.rodata ALIGN(4K) : {
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
} :rodata
|
||||||
|
|
||||||
|
.data ALIGN(4K) : {
|
||||||
|
*(.data .data.*)
|
||||||
|
} :data
|
||||||
|
|
||||||
|
/* .bss occupies memory but not file space. The loader zeroes it via the
|
||||||
|
* gap between each PT_LOAD segment's file size and memory size, so no
|
||||||
|
* boundary symbols are needed here. (Zig's self-hosted linker also does not
|
||||||
|
* yet honour linker-script symbol assignments.) */
|
||||||
|
.bss ALIGN(4K) : {
|
||||||
|
*(.bss .bss.*)
|
||||||
|
*(COMMON)
|
||||||
|
} :data
|
||||||
|
|
||||||
|
/DISCARD/ : {
|
||||||
|
*(.comment)
|
||||||
|
*(.note .note.*)
|
||||||
|
*(.eh_frame .eh_frame_hdr)
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main.zig
48
src/main.zig
|
|
@ -1,8 +1,48 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
|
||||||
|
|
||||||
const danos = @import("danos");
|
const danos = @import("danos");
|
||||||
|
const BootInfo = danos.BootInfo;
|
||||||
|
|
||||||
pub fn main() !void {
|
/// The calling convention used to enter the kernel. Pinned to SysV explicitly:
|
||||||
|
/// the bootloader is built for the UEFI target, whose C convention is Microsoft
|
||||||
|
/// x64 (first argument in RCX), while the kernel is SysV (first argument in
|
||||||
|
/// RDI). Both sides reference this so the `boot_info` pointer lands in the
|
||||||
|
/// register the other expects. `danos.kernel_abi` re-exports it to the loader.
|
||||||
|
pub const kernel_abi = danos.kernel_abi;
|
||||||
|
|
||||||
}
|
/// Kernel entry point. The bootloader jumps here after `ExitBootServices` with a
|
||||||
|
/// pointer to the handoff data. There is no runtime, no stack unwinding, and no
|
||||||
|
/// caller to return to, so this never returns.
|
||||||
|
export fn _start(boot_info: *const BootInfo) callconv(kernel_abi) noreturn {
|
||||||
|
kmain(boot_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kmain(boot_info: *const BootInfo) noreturn {
|
||||||
|
// Paint the whole screen so it is visually obvious the kernel took over.
|
||||||
|
// We honour `pitch` because it can exceed `width * 4`.
|
||||||
|
const fb = boot_info.framebuffer;
|
||||||
|
const base: [*]volatile u8 = @ptrFromInt(fb.base);
|
||||||
|
var y: u32 = 0;
|
||||||
|
while (y < fb.height) : (y += 1) {
|
||||||
|
const row: [*]volatile u32 = @ptrCast(@alignCast(base + y * fb.pitch));
|
||||||
|
var x: u32 = 0;
|
||||||
|
while (x < fb.width) : (x += 1) row[x] = 0x0033_66cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
hang();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the CPU. `hlt` in a loop parks the core at near-zero power until the
|
||||||
|
/// next interrupt; we loop because `hlt` returns when one arrives.
|
||||||
|
fn hang() noreturn {
|
||||||
|
while (true) asm volatile ("hlt");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Freestanding has no OS to receive a panic, so override std's default panic
|
||||||
|
/// handler to simply halt. (Later this can print to the framebuffer/serial.)
|
||||||
|
pub const panic = std.debug.FullPanic(struct {
|
||||||
|
fn panic(msg: []const u8, first_trace_addr: ?usize) noreturn {
|
||||||
|
_ = msg;
|
||||||
|
_ = first_trace_addr;
|
||||||
|
hang();
|
||||||
|
}
|
||||||
|
}.panic);
|
||||||
|
|
|
||||||
40
src/root.zig
40
src/root.zig
|
|
@ -1 +1,39 @@
|
||||||
//! By convention, root.zig is the root source file when making a package.
|
//! Shared definitions that form the contract between the bootloader
|
||||||
|
//! (src/efi.zig, built as BOOTX64.efi) and the kernel (src/main.zig).
|
||||||
|
//!
|
||||||
|
//! Both binaries import this as the "danos" module, so the handoff layout is
|
||||||
|
//! defined in exactly one place.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Calling convention for the bootloader→kernel jump. Pinned to SysV so it does
|
||||||
|
/// not depend on each binary's target default: the UEFI bootloader's C
|
||||||
|
/// convention is Microsoft x64 (first arg in RCX), the freestanding kernel's is
|
||||||
|
/// SysV (first arg in RDI). Both reference this to agree on where `*BootInfo`
|
||||||
|
/// is passed.
|
||||||
|
pub const kernel_abi: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} };
|
||||||
|
|
||||||
|
/// Pixel byte order of the linear framebuffer the firmware handed us.
|
||||||
|
pub const PixelFormat = enum(u32) {
|
||||||
|
/// Byte 0 = Red, 1 = Green, 2 = Blue, 3 = reserved.
|
||||||
|
rgbx,
|
||||||
|
/// Byte 0 = Blue, 1 = Green, 2 = Red, 3 = reserved.
|
||||||
|
bgrx,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A linear framebuffer: `width`x`height` pixels, each a 32-bit value, with
|
||||||
|
/// `pitch` bytes between the start of one row and the next (which may be larger
|
||||||
|
/// than `width * 4` due to hardware padding).
|
||||||
|
pub const Framebuffer = extern struct {
|
||||||
|
base: usize,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
pitch: u32,
|
||||||
|
format: PixelFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Handoff structure the bootloader fills in and passes to the kernel's
|
||||||
|
/// `_start` in RDI (the first argument under the SysV AMD64 C ABI).
|
||||||
|
pub const BootInfo = extern struct {
|
||||||
|
framebuffer: Framebuffer,
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue