From daa043763956c0fc48ab97f5e2e190baee792dc4 Mon Sep 17 00:00:00 2001 From: break27 Date: Sun, 1 Jun 2025 12:30:59 +0800 Subject: [PATCH] initial commit --- .gitignore | 3 + .zigversion | 1 + build.zig | 162 +++++++++++++++++++++++++++++ build.zig.zon | 61 +++++++++++ limine.conf | 10 ++ src/root.zig | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 518 insertions(+) create mode 100644 .gitignore create mode 100644 .zigversion create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100755 limine.conf create mode 100644 src/root.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f73c045 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.vscode +/.zig-cache +/zig-out \ No newline at end of file diff --git a/.zigversion b/.zigversion new file mode 100644 index 0000000..0548fb4 --- /dev/null +++ b/.zigversion @@ -0,0 +1 @@ +0.14.0 \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..e8d14cb --- /dev/null +++ b/build.zig @@ -0,0 +1,162 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + // This creates a "module", which represents a collection of source files alongside + // some compilation options, such as optimization mode and linked system libraries. + // Every executable or library we compile will be based on one or more modules. + const mod = b.addModule("bootloader", .{ + // `root_source_file` is the Zig "entry point" of the module. If a module + // only contains e.g. external object files, you can make this `null`. + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + const kernel = b.option([]const u8, "kernel", "Path to kernel executable"); + const output = b.option([]const u8, "output", "Output image name"); + + const image = buildBootableImage(b, b.path(kernel orelse "kernel"), target.result.cpu.arch); + const install = b.addInstallFile(image, output orelse "bootimage.iso"); + b.getInstallStep().dependOn(&install.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const unit_tests = b.addTest(.{ + .root_module = mod, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} + +pub fn buildBootableImage(b: *std.Build, kernel: std.Build.LazyPath, arch: std.Target.Cpu.Arch) std.Build.LazyPath { + // Add Limine to the dependency tree. + const limine = b.dependency("limine", .{}); + const rootfs = b.addNamedWriteFiles("rootfs"); + + const exe = b.addExecutable(.{ + .name = "limine", + .target = b.resolveTargetQuery(.{}), + .optimize = .ReleaseSafe, + }); + + exe.addCSourceFile(.{ + .file = limine.path("limine.c"), + .flags = &.{"-std=c99"}, + }); + exe.linkLibC(); + + _ = rootfs.addCopyFile(b.path("limine.conf"), "boot/limine/limine.conf"); + _ = rootfs.addCopyFile(kernel, "boot/kernel"); + + const run_cmd = b.addSystemCommand(switch (arch) { + .aarch64 => block: { + _ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin"); + _ = rootfs.addCopyFile(limine.path("BOOTAA64.EFI"), "EFI/BOOT/BOOTAA64.EFI"); + + break :block &.{ + "xorriso", "-as", "mkisofs", + "-R", "-r", "-J", + "-hfsplus", + "-apm-block-size", "2048", + "--efi-boot", "boot/limine/limine-uefi-cd.bin", + "-efi-boot-part", + "--efi-boot-image", + "--protective-msdos-label", + }; + }, + .loongarch64 => block: { + _ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin"); + _ = rootfs.addCopyFile(limine.path("BOOTLOONGARCH64.EFI"), "EFI/BOOT/BOOTLOONGARCH64.EFI"); + + break :block &.{ + "xorriso", "-as", "mkisofs", + "-R", "-r", "-J", + "-hfsplus", + "-apm-block-size", "2048", + "--efi-boot", "boot/limine/limine-uefi-cd.bin", + "-efi-boot-part", + "--efi-boot-image", + "--protective-msdos-label", + }; + }, + .riscv64 => block: { + _ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin"); + _ = rootfs.addCopyFile(limine.path("BOOTRISCV64.EFI"), "EFI/BOOT/BOOTRISCV64.EFI"); + + break :block &.{ + "xorriso", "-as", "mkisofs", + "-R", "-r", "-J", + "-hfsplus", + "-apm-block-size", "2048", + "--efi-boot", "boot/limine/limine-uefi-cd.bin", + "-efi-boot-part", + "--efi-boot-image", + "--protective-msdos-label", + }; + }, + .x86_64 => block: { + _ = rootfs.addCopyFile(limine.path("limine-bios.sys"), "boot/limine/limine-bios.sys"); + _ = rootfs.addCopyFile(limine.path("limine-bios-cd.bin"), "boot/limine/limine-bios-cd.bin"); + _ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin"); + _ = rootfs.addCopyFile(limine.path("BOOTX64.EFI"), "EFI/BOOT/BOOTX64.EFI"); + _ = rootfs.addCopyFile(limine.path("BOOTIA32.EFI"), "EFI/BOOT/BOOTIA32.EFI"); + + break :block &.{ + "xorriso", "-as", "mkisofs", + "-R", "-r", "-J", + "-b", "boot/limine/limine-bios-cd.bin", + "-no-emul-boot", + "-boot-load-size", "4", + "-boot-info-table", + "-hfsplus", + "-apm-block-size", "2048", + "--efi-boot", "boot/limine/limine-uefi-cd.bin", + "-efi-boot-part", + "--efi-boot-image", + "--protective-msdos-label", + }; + }, + else => unreachable, + }); + + run_cmd.step.dependOn(&rootfs.step); + run_cmd.addDirectoryArg(rootfs.getDirectory()); + run_cmd.addArg("-o"); + // Retrieve the bootable image from command. + const raw = run_cmd.addOutputFileArg("raw.iso"); + + // Install Limine stage 1 and 2 for legacy BIOS boot. + // This is only required on x86_64, but we are deploying for all anyway. + const deploy = b.addRunArtifact(exe); + deploy.step.dependOn(&run_cmd.step); + deploy.addArg("bios-install"); + deploy.addFileArg(raw); + + const wf = b.addWriteFiles(); + const image = wf.addCopyFile(raw, "image.iso"); + wf.step.dependOn(&deploy.step); + + return image; +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..4bfb0aa --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,61 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .bootloader, + + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.1", + + // Together with name, this represents a globally unique package + // identifier. This field is generated by the Zig toolchain when the + // package is first created, and then *never changes*. This allows + // unambiguous detection of one package being an updated version of + // another. + // + // When forking a Zig project, this id should be regenerated (delete the + // field and run `zig build`) if the upstream project is still maintained. + // Otherwise, the fork is *hostile*, attempting to take control over the + // original project's identity. Thus it is recommended to leave the comment + // on the following line intact, so that it shows up in code reviews that + // modify the field. + .fingerprint = 0xaa3fe7b4f8f014ec, // Changing this has security and trust implications. + + // Tracks the earliest Zig version that the package considers to be a + // supported use case. + .minimum_zig_version = "0.14.0", + + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + .limine = .{ + .url = "https://github.com/limine-bootloader/limine/archive/refs/heads/v8.x-binary.tar.gz", + .hash = "N-V-__8AAKv8RwBBK__rVVriqhprGLIErfPKu89slFBdUT2F", + }, + }, + + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/limine.conf b/limine.conf new file mode 100755 index 0000000..fed1ca4 --- /dev/null +++ b/limine.conf @@ -0,0 +1,10 @@ +# Timeout in seconds that Limine will use before automatically booting. +timeout: 0 + +# The entry name that will be displayed in the boot menu. +/Tangerine + # We use the Limine boot protocol. + protocol: limine + + # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. + kernel_path: boot():/boot/kernel \ No newline at end of file diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..366254a --- /dev/null +++ b/src/root.zig @@ -0,0 +1,281 @@ +const builtin = @import("builtin"); + +pub fn magic(a: u64, b: u64) [4]u64 { + return .{ 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, a, b }; +} + +pub const BaseRevision = extern struct { + id: [2]u64 = .{ 0xf9562b2d5c95a6c8, 0x6a7b384944536bdc }, + revision: u64, + + pub fn is_supported(self: *const volatile @This()) bool { + return self.revision == 0; + } +}; + +pub const BootloaderInfo = struct { + pub const Request = extern struct { + id: [4]u64 = magic(0xf55038d8e2a1202f, 0x279426fcf5f59740), + revision: u64 = 0, + response: ?*Response = null, + }; + + pub const Response = extern struct { + revision: u64, + name: [*:0]u8, + version: [*:0]u8, + }; +}; + +pub const BootTime = struct { + pub const Request = extern struct { + id: [4]u64 = magic(0x502746e184c088aa, 0xfbc5ec83e6327893), + revision: u64 = 0, + response: ?*Response = null, + }; + + pub const Response = extern struct { + revision: u64, + boot_time: i64, + }; +}; + +pub const File = extern struct { + revision: u64, + address: [*]u8, + size: u64, + path: [*:0]u8, + cmdline: [*:0]u8, + media_type: MediaType, + unused: u32, + tftp_ip: u32, + tftp_port: u32, + partition_index: u32, + mbr_disk_id: u32, + gpt_disk_uuid: Uuid, + gpt_part_uuid: Uuid, + part_uuid: Uuid, + + pub const Uuid = extern struct { + a: u32, + b: u16, + c: u16, + d: [8]u8, + }; + + pub const MediaType = enum(u32) { + generic = 0, + optical = 1, + tftp = 2, + }; +}; + +pub const Framebuffer = extern struct { + address: [*]u8, + width: u64, + height: u64, + pitch: u64, + bpp: u16, + memory_model: MemoryModel, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, + unused: [7]u8, + edid_size: u64, + edid: ?[*]u8, + + // Response revision 1 + mode_count: u64, + modes: [*]*VideoMode, + + pub const MemoryModel = enum(u8) { + rgb = 1, + }; + + pub const VideoMode = extern struct { + pitch: u64, + width: u64, + height: u64, + bpp: u16, + memory_model: MemoryModel, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, + }; + + pub const Request = extern struct { + id: [4]u64 = magic(0x9d5827dcd881dd75, 0xa3148604f6fab11b), + revision: u64 = 1, + response: ?*Response = null + }; + + pub const Response = extern struct { + revision: u64, + framebuffer_count: u64, + framebuffers: [*]*Framebuffer, + + pub inline fn getFramebuffers(self: *@This()) []*Framebuffer { + return self.framebuffers[0..self.framebuffer_count]; + } + }; + + pub inline fn getEdid(self: *@This()) ?[]u8 { + if (self.edid) |edid| { + return edid[0..self.edid_size]; + } + } + + pub inline fn getModes(self: *@This()) []*VideoMode { + return self.modes[0..self.mode_count]; + } +}; + +pub const Hhdm = struct { + pub const Request = extern struct { + id: [4]u64 = magic(0x48dcf1cb8ad2b852, 0x63984e959a98244b), + revision: u64 = 0, + response: ?*Response = null, + }; + + pub const Response = extern struct { + revision: u64, + offset: u64, + }; +}; + +pub const Rsdp = struct { + pub const Request = extern struct { + id: [4]u64 = magic(0xc5e77b6b397e7b43, 0x27637845accdcf3c), + revision: u64 = 0, + response: ?*Response = null, + }; + + pub const Response = extern struct { + revision: u64, + address: u64, + }; +}; + +pub const MemoryMap = struct { + pub const Request = extern struct { + id: [4]u64 = magic(0x67cf3d9d378a806f, 0xe304acdfc50c3c62), + revision: u64 = 0, + response: ?*Response = null, + }; + + pub const Response = extern struct { + revision: u64, + entry_count: u64, + entries: [*]*Entry, + + pub inline fn getEntries(self: *@This()) []*Entry { + return self.entries[0..self.entry_count]; + } + }; + + pub const Entry = extern struct { + base: u64, + length: u64, + type: EntryType, + }; + + pub const EntryType = enum(u64) { + usable = 0, + reserved = 1, + acpi_reclaimable = 2, + acpi_nvs = 3, + bad_memory = 4, + bootloader_reclaimable = 5, + kernel_and_modules = 6, + framebuffer = 7, + }; +}; + +pub const Paging = struct { + pub const Mode = switch (builtin.cpu.arch) { + .aarch64, .x86_64 => enum(u64) { + four_level, + five_level, + + pub inline fn default() Mode { + return Mode.four_level; + } + + pub inline fn max() Mode { + return Mode.five_level; + } + + pub inline fn min() Mode { + return Mode.four_level; + } + }, + .loongarch64 => enum(u64) { + four_level, + + pub inline fn default() Mode { + return Mode.four_level; + } + + pub inline fn max() Mode { + return Mode.four_level; + } + + pub inline fn min() Mode { + return Mode.four_level; + } + }, + .riscv64 => enum(u64) { + sv39, + sv48, + sv57, + + pub inline fn default() Mode { + return Mode.sv48; + } + + pub inline fn max() Mode { + return Mode.sv57; + } + + pub inline fn min() Mode { + return Mode.sv39; + } + }, + else => |x| @compileError("Unsupported architecture: " ++ @tagName(x)), + }; + + pub const Request = extern struct { + id: [4]u64 = magic(0x95c1a0edab0944cb, 0xa4e5cb3842f7488a), + revision: u64 = 0, + response: ?*Response = null, + mode: Mode = .default(), + + // Revision 1+ + max_mode: Mode = .max(), + min_mode: Mode = .min(), + }; + + pub const Response = extern struct { + revision: u64, + mode: Mode + }; +}; + +pub const DeviceTreeBlob = struct { + pub const Request = extern struct { + id: [4]u64 = magic(0xb40ddb48fb54bac7, 0x545081493f81ffb7), + revision: u64 = 0, + response: ?*Response = null, + }; + + pub const Response = extern struct { + revision: u64, + pointer: ?*anyopaque, + }; +};