diff --git a/build.zig b/build.zig index afcde7e..a8d071d 100644 --- a/build.zig +++ b/build.zig @@ -80,6 +80,9 @@ pub fn build(b: *std.Build) void { const kernel = addKernel(b, plat, optimize, config_module, ksyms_module, rtl_module, arch_module); kernel.root_module.addAssemblyFile(ksyms_s); + const loader = addLoader(b, plat, optimize, config_module, rtl_module, arch_module); + + b.default_step.dependOn(&b.addInstallArtifact(loader, .{}).step); b.default_step.dependOn(&b.addInstallArtifact(kernel, .{}).step); const docs_module = b.createModule(.{ @@ -108,10 +111,10 @@ pub fn build(b: *std.Build) void { docs_step.dependOn(&install_docs.step); const iso_step = b.step("iso", "Build the ISO image"); - const result = image.addIso(b, plat.arch, "myos", kernel, b.dependency("limine", .{})); + const result = image.addIso(b, plat.arch, "myos", kernel, loader, b.dependency("limine", .{})); iso_step.dependOn(result.step); - run.addRun(b, kernel, plat); + run.addRun(b, kernel, loader, plat); const rtl_tests = b.addTest(.{ .root_module = b.createModule(.{ @@ -173,6 +176,8 @@ fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.Optimiz kernel.root_module.addImport("ksyms", ksyms_module); kernel.root_module.addImport("rtl", rtl); + kernel.link_gc_sections = false; + kernel.use_llvm = true; kernel.use_lld = true; @@ -215,7 +220,65 @@ fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.Optimiz else => {}, } - kernel.linker_script = b.path(b.fmt("build/linker-scripts/{s}.lds", .{@tagName(plat.arch)})); + kernel.linker_script = b.path(b.fmt("build/linker-scripts/kernel/{s}.lds", .{@tagName(plat.arch)})); return kernel; } + +fn addLoader(b: *std.Build, plat: config.Platform, optimize: std.builtin.OptimizeMode, config_module: *std.Build.Module, rtl: *std.Build.Module, arch_module: *std.Build.Module) *std.Build.Step.Compile { + const target = b.resolveTargetQuery(targetQueryForPlatform(plat)); + + const name = b.fmt("loader-{s}-{s}", .{ @tagName(plat.arch), @tagName(plat.bootloader) }); + + const loader = b.addExecutable(.{ + .name = name, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/loader/root.zig"), + .target = target, + .optimize = optimize, + }), + }); + + loader.root_module.addImport("arch", arch_module); + loader.root_module.addImport("config", config_module); + loader.root_module.addImport("rtl", rtl); + loader.root_module.addAssemblyFile(b.path("src/loader/jump.s")); + + loader.use_llvm = true; + loader.use_lld = true; + + loader.root_module.omit_frame_pointer = false; + + loader.entry = .{ .symbol_name = "loader_entry" }; + + const modules: []const *std.Build.Module = &.{ arch_module, config_module, rtl, loader.root_module }; + + loader.linkage = .static; + loader.pie = false; + + for (modules) |m| { + m.pic = false; + m.strip = false; + m.stack_check = false; + m.stack_protector = false; + m.unwind_tables = .none; + m.omit_frame_pointer = false; + + m.code_model = switch (plat.arch) { + .x86_64 => .kernel, + .riscv64 => .medium, + else => .large, + }; + + switch (plat.arch) { + .x86_64 => { + m.red_zone = false; + }, + else => {}, + } + } + + loader.linker_script = b.path(b.fmt("build/linker-scripts/loader/{s}.lds", .{@tagName(plat.arch)})); + + return loader; +} diff --git a/build/config.zig b/build/config.zig index a6db091..2f05030 100644 --- a/build/config.zig +++ b/build/config.zig @@ -9,8 +9,7 @@ pub const Bootloader = enum { pub const Platform = struct { arch: std.Target.Cpu.Arch, os: std.Target.Os.Tag, - - bootloader: ?Bootloader = null, + bootloader: Bootloader = .Limine, }; const ArchEnum = enum { diff --git a/build/image.zig b/build/image.zig index 80e2f69..c3dbc0b 100644 --- a/build/image.zig +++ b/build/image.zig @@ -21,20 +21,24 @@ pub fn addIso( arch: Arch, image_name: []const u8, kernel: *std.Build.Step.Compile, + loader: *std.Build.Step.Compile, limine: *std.Build.Dependency, ) IsoResult { const limine_tool = addLimineTool(b, limine); const kernel_bin = kernel.getEmittedBin(); + const loader_bin = loader.getEmittedBin(); const prep_script = \\set -euo pipefail \\kernel="$1" - \\iso_root="$2" - \\limine_conf="$3" - \\limine_dir="$4" - \\arch="$5" + \\loader="$2" + \\iso_root="$3" + \\limine_conf="$4" + \\limine_dir="$5" + \\arch="$6" \\rm -rf "$iso_root" \\mkdir -p "$iso_root/boot/limine" "$iso_root/EFI/BOOT" + \\cp "$loader" "$iso_root/boot/loader" \\cp "$kernel" "$iso_root/boot/kernel" \\cp "$limine_conf" "$iso_root/boot/limine/limine.conf" \\case "$arch" in @@ -62,6 +66,7 @@ pub fn addIso( const prep = b.addSystemCommand(&.{ "bash", "-ceu", prep_script, "--" }); prep.addFileArg(kernel_bin); + prep.addFileArg(loader_bin); prep.addDirectoryArg(iso_root.getDirectory()); prep.addFileArg(b.path("build/limine.conf")); prep.addDirectoryArg(limine.path(".")); diff --git a/build/limine.conf b/build/limine.conf index 05e5683..8385c74 100644 --- a/build/limine.conf +++ b/build/limine.conf @@ -7,5 +7,8 @@ timeout: 3 protocol: limine # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. - path: boot():/boot/kernel + path: boot():/boot/loader + + module_path: boot():/boot/kernel + module_string: kernel diff --git a/build/linker-scripts/aarch64.lds b/build/linker-scripts/aarch64.lds deleted file mode 100644 index 5cc6687..0000000 --- a/build/linker-scripts/aarch64.lds +++ /dev/null @@ -1,75 +0,0 @@ -/* Tell the linker that we want an aarch64 ELF64 output file */ -OUTPUT_FORMAT(elf64-littleaarch64) - -/* We want the symbol kmain to be our entry point */ -ENTRY(kmain) - -/* Define the program headers we want so the bootloader gives us the right */ -/* MMU permissions; this also allows us to exert more control over the linking */ -/* process. */ -PHDRS -{ - limine_requests PT_LOAD; - text PT_LOAD; - rodata PT_LOAD; - data PT_LOAD; -} - -SECTIONS -{ - /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ - /* and because that is what the Limine spec mandates. */ - /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ - /* that is the beginning of the region. */ - . = 0xffffffff80000000; - - /* Define a section to contain the Limine requests and assign it to its own PHDR */ - .limine_requests : { - KEEP(*(.limine_requests_start)) - KEEP(*(.limine_requests)) - KEEP(*(.limine_requests_end)) - } :limine_requests - - /* Move to the next memory page for .text */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .text : { - *(.text .text.*) - } :text - - /* Move to the next memory page for .rodata */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .rodata : { - *(.rodata .rodata.*) - } :rodata - - /* Add a .note.gnu.build-id output section in case a build ID flag is added to the */ - /* linker command. */ - .note.gnu.build-id : { - *(.note.gnu.build-id) - } :rodata - - /* Move to the next memory page for .data */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .data : { - *(.data .data.*) - } :data - - /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ - /* unnecessary zeros will be written to the binary. */ - /* If you need, for example, .init_array and .fini_array, those should be placed */ - /* above this. */ - .bss : { - *(.bss .bss.*) - *(COMMON) - } :data - - /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ - /DISCARD/ : { - *(.eh_frame*) - *(.note .note.*) - } -} - diff --git a/build/linker-scripts/x86_64.lds b/build/linker-scripts/kernel/x86_64.lds similarity index 50% rename from build/linker-scripts/x86_64.lds rename to build/linker-scripts/kernel/x86_64.lds index 9303a5f..601436f 100644 --- a/build/linker-scripts/x86_64.lds +++ b/build/linker-scripts/kernel/x86_64.lds @@ -1,12 +1,7 @@ -/* Tell the linker that we want an x86_64 ELF64 output file */ OUTPUT_FORMAT(elf64-x86-64) -/* We want the symbol kmain to be our entry point */ ENTRY(kmain) -/* Define the program headers we want so the bootloader gives us the right */ -/* MMU permissions; this also allows us to exert more control over the linking */ -/* process. */ PHDRS { limine_requests PT_LOAD; @@ -17,22 +12,20 @@ PHDRS SECTIONS { - /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ - /* and because that is what the Limine spec mandates. */ - /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ - /* that is the beginning of the region. */ . = 0xffffffff80000000; - /* Define a section to contain the Limine requests and assign it to its own PHDR */ + .limine_requests : { KEEP(*(.limine_requests_start)) KEEP(*(.limine_requests)) KEEP(*(.limine_requests_end)) } :limine_requests - /* Move to the next memory page for .text */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + text_start_addr = .; .text : { @@ -40,11 +33,9 @@ SECTIONS } :text - /* Move to the next memory page for .rodata */ . = ALIGN(CONSTANT(MAXPAGESIZE)); text_end_addr = .; - rodata_start_addr = .; .rodata : { @@ -52,14 +43,11 @@ SECTIONS } :rodata - /* Add a .note.gnu.build-id output section in case a build ID flag is added to the */ - /* linker command. */ .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata - /* Move to the next memory page for .data */ . = ALIGN(CONSTANT(MAXPAGESIZE)); rodata_end_addr = .; @@ -83,10 +71,7 @@ SECTIONS } :data - /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ - /* unnecessary zeros will be written to the binary. */ - /* If you need, for example, .init_array and .fini_array, those should be placed */ - /* above this. */ + .bss : { *(.bss .bss.*) *(COMMON) @@ -97,7 +82,6 @@ SECTIONS data_end_addr = .; - /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ /DISCARD/ : { *(.eh_frame*) *(.note .note.*) diff --git a/build/linker-scripts/loader/x86_64.lds b/build/linker-scripts/loader/x86_64.lds new file mode 100644 index 0000000..196122e --- /dev/null +++ b/build/linker-scripts/loader/x86_64.lds @@ -0,0 +1,68 @@ +OUTPUT_FORMAT(elf64-x86-64) +ENTRY(loader_entry) + +PHDRS +{ + limine_requests PT_LOAD; + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; +} + +SECTIONS +{ + . = 0xffffffffe0000000; + + .limine_requests : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + } :limine_requests + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + text_start_addr = .; + + .text : { + *(.text .text.*) + } :text + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + text_end_addr = .; + + rodata_start_addr = .; + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + .note.gnu.build-id : { + *(.note.gnu.build-id) + } :rodata + + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + rodata_end_addr = .; + data_start_addr = .; + + .data : { + *(.data .data.*) + } :data + + + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + data_end_addr = .; + + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/build/linker-scripts/loongarch64.lds b/build/linker-scripts/loongarch64.lds deleted file mode 100644 index a935e85..0000000 --- a/build/linker-scripts/loongarch64.lds +++ /dev/null @@ -1,75 +0,0 @@ -/* Tell the linker that we want a loongarch64 ELF64 output file */ -OUTPUT_FORMAT(elf64-loongarch) - -/* We want the symbol kmain to be our entry point */ -ENTRY(kmain) - -/* Define the program headers we want so the bootloader gives us the right */ -/* MMU permissions; this also allows us to exert more control over the linking */ -/* process. */ -PHDRS -{ - limine_requests PT_LOAD; - text PT_LOAD; - rodata PT_LOAD; - data PT_LOAD; -} - -SECTIONS -{ - /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ - /* and because that is what the Limine spec mandates. */ - /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ - /* that is the beginning of the region. */ - . = 0xffffffff80000000; - - /* Define a section to contain the Limine requests and assign it to its own PHDR */ - .limine_requests : { - KEEP(*(.limine_requests_start)) - KEEP(*(.limine_requests)) - KEEP(*(.limine_requests_end)) - } :limine_requests - - /* Move to the next memory page for .text */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .text : { - *(.text .text.*) - } :text - - /* Move to the next memory page for .rodata */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .rodata : { - *(.rodata .rodata.*) - } :rodata - - /* Add a .note.gnu.build-id output section in case a build ID flag is added to the */ - /* linker command. */ - .note.gnu.build-id : { - *(.note.gnu.build-id) - } :rodata - - /* Move to the next memory page for .data */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .data : { - *(.data .data.*) - } :data - - /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ - /* unnecessary zeros will be written to the binary. */ - /* If you need, for example, .init_array and .fini_array, those should be placed */ - /* above this. */ - .bss : { - *(.bss .bss.*) - *(COMMON) - } :data - - /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ - /DISCARD/ : { - *(.eh_frame*) - *(.note .note.*) - } -} - diff --git a/build/linker-scripts/riscv64.lds b/build/linker-scripts/riscv64.lds deleted file mode 100644 index f5f1cda..0000000 --- a/build/linker-scripts/riscv64.lds +++ /dev/null @@ -1,75 +0,0 @@ -/* Tell the linker that we want a riscv64 ELF64 output file */ -OUTPUT_FORMAT(elf64-littleriscv) - -/* We want the symbol kmain to be our entry point */ -ENTRY(kmain) - -/* Define the program headers we want so the bootloader gives us the right */ -/* MMU permissions; this also allows us to exert more control over the linking */ -/* process. */ -PHDRS -{ - limine_requests PT_LOAD; - text PT_LOAD; - rodata PT_LOAD; - data PT_LOAD; -} - -SECTIONS -{ - /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ - /* and because that is what the Limine spec mandates. */ - /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ - /* that is the beginning of the region. */ - . = 0xffffffff80000000; - - /* Define a section to contain the Limine requests and assign it to its own PHDR */ - .limine_requests : { - KEEP(*(.limine_requests_start)) - KEEP(*(.limine_requests)) - KEEP(*(.limine_requests_end)) - } :limine_requests - - /* Move to the next memory page for .text */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .text : { - *(.text .text.*) - } :text - - /* Move to the next memory page for .rodata */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .rodata : { - *(.rodata .rodata.*) - } :rodata - - /* Add a .note.gnu.build-id output section in case a build ID flag is added to the */ - /* linker command. */ - .note.gnu.build-id : { - *(.note.gnu.build-id) - } :rodata - - /* Move to the next memory page for .data */ - . = ALIGN(CONSTANT(MAXPAGESIZE)); - - .data : { - *(.data .data.*) - } :data - - /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ - /* unnecessary zeros will be written to the binary. */ - /* If you need, for example, .init_array and .fini_array, those should be placed */ - /* above this. */ - .bss : { - *(.bss .bss.*) - *(COMMON) - } :data - - /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ - /DISCARD/ : { - *(.eh_frame*) - *(.note .note.*) - } -} - diff --git a/build/linker-scripts/uml.lds b/build/linker-scripts/uml.lds deleted file mode 100644 index 65b3778..0000000 --- a/build/linker-scripts/uml.lds +++ /dev/null @@ -1,17 +0,0 @@ -SECTIONS { - .data.percpu : { - __percpu_start = .; - *(.data.percpu) - __percpu_end = .; - } - -.percpu_init_array : { - __init_array_percpu_start = .; - KEEP(*(.percpu_init_array)) - __init_array_percpu_end = .; - } - -} INSERT AFTER .data; - -PROVIDE(text_start_addr = ADDR(.text)); -PROVIDE(text_end_addr = ADDR(.text) + SIZEOF(.text)); diff --git a/build/run.zig b/build/run.zig index 4b15c8b..a115aab 100644 --- a/build/run.zig +++ b/build/run.zig @@ -8,7 +8,7 @@ const QemuArgs = struct { args: [][]u8, }; -pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, plat: config.Platform) void { +pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, loader: *std.Build.Step.Compile, plat: config.Platform) void { const run_step = b.step("run", "Run the kernel"); const uefi = b.option(bool, "uefi", "Boot with UEFI firmware") orelse false; const debug = b.option(bool, "debug", "Boot in debug mode") orelse false; @@ -17,7 +17,7 @@ pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, plat: config.Platf if (plat.os == .freestanding) { const limine = b.dependency("limine", .{}); const ovmf = b.dependency("ovmf", .{}); - const iso_out = image.addIso(b, plat.arch, "myos", kernel, limine); + const iso_out = image.addIso(b, plat.arch, "myos", kernel, loader, limine); const qemu_bin = switch (plat.arch) { .x86_64 => "qemu-system-x86_64", @@ -47,8 +47,8 @@ pub fn addRun(b: *std.Build, kernel: *std.Build.Step.Compile, plat: config.Platf qemu.addArgs(&.{ "-d", "int", - "-s", - "-S", + //"-s", + //"-S", "-D", "qemu.log", "-no-reboot", diff --git a/src/boot/limine/entry.zig b/src/boot/limine/entry.zig deleted file mode 100644 index 74f7405..0000000 --- a/src/boot/limine/entry.zig +++ /dev/null @@ -1,100 +0,0 @@ -const std = @import("std"); - -const r = @import("root"); -const limine = @import("limine.zig"); - -const pl = r.pl; -const ke = r.ke; -const ki = ke.private; - -pub const std_options = r.std_options; -pub const panic = r.panic; - -var boot_info: pl.BootInfo = undefined; - -pub export var limine_base_revision: [3]u64 linksection(".limine_requests") = limine.base_revision(6); - -pub export var framebuffer_request: limine.FramebufferRequest linksection(".limine_requests") = .{ - .id = limine.framebuffer_request_id, - .revision = 0, - .response = null, -}; - -pub export var limine_requests_start_marker: [4]u64 linksection(".limine_requests_start") = limine.requests_start_marker; -pub export var limine_requests_end_marker: [2]u64 linksection(".limine_requests_end") = limine.requests_end_marker; - -pub export var memmap_request: limine.MemmapRequest linksection(".limine_requests") = .{ - .id = limine.memmap_request_id, - .revision = 0, - .response = null, -}; - -pub export var rsdp_request: limine.RsdpRequest linksection(".limine_requests") = .{ - .id = limine.rsdp_request_id, - .revision = 0, - .response = null, -}; - -pub export var cmdline_request: limine.CmdlineRequest linksection(".limine_requests") = .{ - .id = limine.cmdline_request_id, - .revision = 0, - .response = null, -}; - -pub export var kernel_address_request: limine.ExecutableAddressRequest linksection(".limine_requests") = .{ - .id = limine.executable_address_request_id, - .revision = 0, - .response = null, -}; - -fn build_memory_map() void { - const memmap_response = memmap_request.response orelse { - @panic("Limine memmap request failed"); - }; - - boot_info.memory_map.entry_count = memmap_response.entry_count; - const entries = memmap_response.entries orelse { - @panic("Limine memmap response has no entries"); - }; - - for (0..memmap_response.entry_count) |i| { - const entry = entries[i]; - boot_info.memory_map.entries[i] = .{ - .base = entry.base, - .size = entry.length, - .type = switch (entry.type) { - .Usable => pl.BootInfo.MemMap.Entry.Type.Free, - .AcpiReclaimable => pl.BootInfo.MemMap.Entry.Type.AcpiReclaimable, - .AcpiNvs => pl.BootInfo.MemMap.Entry.Type.AcpiNvs, - .BootloaderReclaimable => pl.BootInfo.MemMap.Entry.Type.LoaderReclaimable, - .KernelAndModules => pl.BootInfo.MemMap.Entry.Type.Kernel, - else => pl.BootInfo.MemMap.Entry.Type.Reserved, - }, - }; - } -} - -export fn kmain() callconv(.c) void { - boot_info.cmdline = if (cmdline_request.response) |cmdline_response| std.mem.span(cmdline_response.cmdline) else null; - - boot_info.rsdp = if (rsdp_request.response) |rsdp_response| @intFromPtr(rsdp_response.rsdp) else null; - - boot_info.kernel_address = if (kernel_address_request.response) |ka_response| .{ - .physical_base = ka_response.physical_base, - .virtual_base = ka_response.virtual_base, - } else .{ .physical_base = 0, .virtual_base = 0 }; - - boot_info.framebuffer = if (framebuffer_request.response) |fb_response| .{ - .address = @intFromPtr(fb_response.framebuffers[0].address), - .width = @intCast(fb_response.framebuffers[0].width), - .height = @intCast(fb_response.framebuffers[0].height), - .pitch = @intCast(fb_response.framebuffers[0].pitch), - .bpp = @intCast(fb_response.framebuffers[0].bpp), - } else null; - - build_memory_map(); - - ke.ncpus = 1; - - ki.init(&boot_info); -} diff --git a/src/ex/fireworks.zig b/src/ex/fireworks.zig index 4529a98..cf35a6e 100644 --- a/src/ex/fireworks.zig +++ b/src/ex/fireworks.zig @@ -217,7 +217,7 @@ fn explodeable(_: ?*anyopaque) void { } pub fn start(param: ?*anyopaque) void { - const boot_info: *r.pl.BootInfo = @ptrCast(@alignCast(param)); + const boot_info: *r.BootInfo = @ptrCast(@alignCast(param)); rand_gen ^= rand_tsc_based(); diff --git a/src/ex/init.zig b/src/ex/init.zig index 5246fe0..0636529 100644 --- a/src/ex/init.zig +++ b/src/ex/init.zig @@ -11,7 +11,7 @@ const rtl = @import("rtl"); // XXX const fbconsole = @import("../dev/fbconsole.zig"); -pub fn init(boot_info: *pl.BootInfo) void { +pub fn init(boot_info: *r.BootInfo) void { mm.init(boot_info); ps.init(); diff --git a/src/kern/entry.zig b/src/kern/entry.zig new file mode 100644 index 0000000..c6f2ca1 --- /dev/null +++ b/src/kern/entry.zig @@ -0,0 +1,16 @@ +const std = @import("std"); + +const r = @import("root"); + +const pl = r.pl; +const ke = r.ke; +const ki = ke.private; + +pub const std_options = r.std_options; +pub const panic = r.panic; + +export fn kmain(info: *r.BootInfo) callconv(.c) void { + ke.ncpus = 1; + + ki.init(info); +} diff --git a/src/kern/init.zig b/src/kern/init.zig index 71c5fde..2240628 100644 --- a/src/kern/init.zig +++ b/src/kern/init.zig @@ -9,7 +9,7 @@ const ex = r.ex; var thread0: ke.Thread = undefined; var stack: [r.kib(16)]u8 align(16) = undefined; -pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { +pub fn init(boot_info: *r.BootInfo) linksection(r.init) void { ki.impl.early_init(); ki.tunable.init(boot_info); std.log.info("Welcome to the machine", .{}); diff --git a/src/kern/tunable.zig b/src/kern/tunable.zig index bb3bccb..e84ca87 100644 --- a/src/kern/tunable.zig +++ b/src/kern/tunable.zig @@ -42,7 +42,7 @@ pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8) }; } -pub fn init(boot_info: *pl.BootInfo) void { +pub fn init(boot_info: *r.BootInfo) void { const cmdline = boot_info.cmdline orelse return; const elems = set.elems(); diff --git a/src/loader/amd64/impl.zig b/src/loader/amd64/impl.zig new file mode 100644 index 0000000..61291d6 --- /dev/null +++ b/src/loader/amd64/impl.zig @@ -0,0 +1,108 @@ +const amd64 = @import("arch"); +const r = @import("root"); +const pmap = @import("../pmap.zig"); + +pub const levels = [_]pmap.PMapLevel{ + .{ .shift = 12, .mask = 0x1ff, .leaf = true }, // 4K + .{ .shift = 21, .mask = 0x1ff, .leaf = true }, // 2M + .{ .shift = 30, .mask = 0x1ff, .leaf = true }, // 1G + .{ .shift = 39, .mask = 0x1ff, .leaf = false }, // root +}; + +pub const virtual_bits: usize = 48; + +pub const Pte = packed struct(u64) { + present: bool, + writable: bool, + user: bool, + write_through: bool, + cache_disable: bool, + accessed: bool, + dirty: bool, + huge_page: bool, + global: bool, + available: u3, + addr: u40, + ignored_2: u11, + nx: bool, + + pub fn address(self: Pte) usize { + return @as(usize, self.addr) << 12; + } + + pub fn is_present(self: Pte) bool { + return self.present; + } + + pub fn load(table: *Pte) Pte { + return @atomicLoad(Pte, table, .monotonic); + } + + pub fn zero() Pte { + return Pte{ + .present = false, + .writable = false, + .user = false, + .write_through = false, + .cache_disable = false, + .accessed = false, + .dirty = false, + .huge_page = false, + .global = false, + .available = 0, + .addr = 0, + .ignored_2 = 0, + .nx = false, + }; + } +}; + +pub inline fn make_table_pte(pa: usize) Pte { + return Pte{ + .present = true, + .writable = true, + .user = true, + .write_through = false, + .cache_disable = false, + .accessed = false, + .dirty = false, + .huge_page = false, + .global = false, + .available = 0, + .addr = @truncate(pa >> 12), + .ignored_2 = 0, + .nx = false, + }; +} + +pub inline fn make_leaf_pte(pa: usize, flags: r.mem.MapFlags, level: usize) Pte { + return Pte{ + .present = true, + .writable = flags.write, + .user = flags.user, + .write_through = flags.write_through, + .cache_disable = flags.cache_disable, + .accessed = false, + .dirty = false, + .huge_page = (level > 0), + .global = flags.global, + .available = 0, + .addr = @truncate(pa >> 12), + .ignored_2 = 0, + .nx = !flags.execute, + }; +} + +pub inline fn activate(root_pa: usize) void { + amd64.write_cr(3, root_pa); +} + +pub fn is_leaf_level_enabled(level: usize) bool { + if (!levels[level].leaf) return false; + if (level == 2) return amd64.cpu_features.gib_pages; + return true; +} + +pub fn debug_write(c: u8) void { + amd64.outb(0xe9, c); +} diff --git a/src/loader/info.zig b/src/loader/info.zig new file mode 100644 index 0000000..f2159b6 --- /dev/null +++ b/src/loader/info.zig @@ -0,0 +1,45 @@ +/// Represents the system memory map. +pub const MemMap = struct { + /// A memory map entry. + pub const Entry = struct { + pub const Type = enum { + Free, + Reserved, + LoaderReclaimable, + AcpiNvs, + AcpiReclaimable, + }; + + type: Type, + base: usize, + size: usize, + }; + + entry_count: usize, + entries: [128]Entry, +}; + +pub const Framebuffer = struct { + address: usize, + width: u32, + height: u32, + pitch: u32, + bpp: u8, +}; + +pub const KernelAddress = struct { physical_base: usize, virtual_base: usize }; + +/// RSDP on ACPI machines +rsdp: ?usize, + +/// Kernel command-line arguments +cmdline: ?[]const u8, + +/// Memory map +memory_map: MemMap, + +/// Framebuffer info, if available +framebuffer: ?Framebuffer, + +/// Kernel address +kernel_address: KernelAddress, diff --git a/src/loader/jump.s b/src/loader/jump.s new file mode 100644 index 0000000..30a6eb4 --- /dev/null +++ b/src/loader/jump.s @@ -0,0 +1,5 @@ +.globl jump_to_kernel +jump_to_kernel: + mov %rsi, %rsp + push $0 + jmp *%rdx ; rdi = loader_info diff --git a/src/loader/limine/impl.zig b/src/loader/limine/impl.zig new file mode 100644 index 0000000..dc054f2 --- /dev/null +++ b/src/loader/limine/impl.zig @@ -0,0 +1,117 @@ +const std = @import("std"); +const limine = @import("limine.zig"); +const r = @import("root"); + +pub export var limine_base_revision: [3]u64 linksection(".limine_requests") = limine.base_revision(6); + +pub export var framebuffer_request: limine.FramebufferRequest linksection(".limine_requests") = .{ + .id = limine.framebuffer_request_id, + .revision = 0, + .response = null, +}; + +pub export var limine_requests_start_marker: [4]u64 linksection(".limine_requests_start") = limine.requests_start_marker; +pub export var limine_requests_end_marker: [2]u64 linksection(".limine_requests_end") = limine.requests_end_marker; + +pub export var memmap_request: limine.MemmapRequest linksection(".limine_requests") = .{ + .id = limine.memmap_request_id, + .revision = 0, + .response = null, +}; + +pub export var rsdp_request: limine.RsdpRequest linksection(".limine_requests") = .{ + .id = limine.rsdp_request_id, + .revision = 0, + .response = null, +}; + +pub export var cmdline_request: limine.CmdlineRequest linksection(".limine_requests") = .{ + .id = limine.cmdline_request_id, + .revision = 0, + .response = null, +}; + +pub export var hhdm_request: limine.HHDMRequest linksection(".limine_requests") = .{ + .id = limine.hhdm_request_id, + .revision = 0, + .response = null, +}; + +pub export var kernel_request: limine.ExecutableAddressRequest linksection(".limine_requests") = .{ + .id = limine.executable_address_request_id, + .revision = 0, + .response = null, +}; + +pub export var module_request: limine.ModuleRequest linksection(".limine_requests") = .{ + .id = limine.module_request_id, + .revision = 0, + .response = null, +}; + +pub fn p2v(pa: usize) usize { + return hhdm_request.response.?.offset + pa; +} + +pub fn get_image_layout() r.ImageLayout { + const resp = kernel_request.response.?; + + return .{ + .physical_base = resp.physical_base, + .virtual_base = resp.virtual_base, + }; +} + +export fn loader_entry() callconv(.c) void { + const mmap = memmap_request.response.?; + + std.log.info("hi", .{}); + + for (0..mmap.entry_count) |i| { + const entry = mmap.entries.?[i]; + + r.mem.add_entry(entry.base, entry.length, switch (entry.type) { + .AcpiNvs => .AcpiNvs, + .BootloaderReclaimable => .LoaderReclaimable, + .AcpiReclaimable => .AcpiReclaimable, + .Usable => .Free, + else => .Reserved, + }); + } + + if (framebuffer_request.response) |resp| { + const fb = resp.framebuffers[0]; + + r.loader_info.framebuffer = .{ + .address = @intFromPtr(fb.address), + .height = @intCast(fb.height), + .width = @intCast(fb.width), + .bpp = @intCast(fb.bpp), + .pitch = @intCast(fb.pitch), + }; + } + + if (rsdp_request.response) |resp| { + r.loader_info.rsdp = @intFromPtr(resp.rsdp); + } + + var kernel: ?*anyopaque = null; + + if (module_request.response) |resp| { + const modules = resp.modules.?[0..resp.module_count]; + + for (modules) |mod| { + const str = std.mem.span(mod.string); + + if (std.mem.eql(u8, str, "kernel")) { + kernel = mod.address; + } + } + } + + if (kernel == null) { + @panic("loader: kernel not found"); + } + + r.main(kernel.?); +} diff --git a/src/boot/limine/limine.zig b/src/loader/limine/limine.zig similarity index 72% rename from src/boot/limine/limine.zig rename to src/loader/limine/limine.zig index 5e06a4d..6e497bd 100644 --- a/src/boot/limine/limine.zig +++ b/src/loader/limine/limine.zig @@ -155,3 +155,71 @@ pub const Framebuffer = extern struct { edid_size: u64, edid: ?*anyopaque, }; + +pub const hhdm_request_id: [4]u64 = .{ + common_magic[0], + common_magic[1], + 0x48dcf1cb8ad2b852, + 0x63984e959a98244b, +}; + +pub const HHDMResponse = extern struct { + revision: u64, + offset: u64, +}; + +pub const HHDMRequest = extern struct { + id: [4]u64, + revision: u64, + response: ?*HHDMResponse, +}; + +pub const module_request_id: [4]u64 = .{ + common_magic[0], + common_magic[1], + 0x3e7e279702be32af, + 0xca1c4f3bd1280cee, +}; + +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, + _, +}; + +const File = extern struct { + revision: u64, + address: *anyopaque, + size: u64, + path: [*:0]u8, + string: [*: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 ModuleResponse = extern struct { + revision: u64, + module_count: u64, + modules: ?[*]*File, +}; + +pub const ModuleRequest = extern struct { + id: [4]u64, + revision: u64, + response: ?*ModuleResponse, +}; diff --git a/src/loader/main.zig b/src/loader/main.zig new file mode 100644 index 0000000..e92d4b2 --- /dev/null +++ b/src/loader/main.zig @@ -0,0 +1,64 @@ +const r = @import("root"); +const std = @import("std"); + +fn load_kernel(kernel: *anyopaque) usize { + const elf: *std.elf.Elf64.Ehdr = @ptrCast(@alignCast(kernel)); + const ident = elf.ident; + + if (!std.mem.eql(u8, ident[0..4], std.elf.MAGIC)) { + @panic("loader: Invalid ELF magic"); + } + + if (ident[std.elf.EI_CLASS] != std.elf.ELFCLASS64) { + @panic("loader: invalid ELF class"); + } + + if (elf.type != std.elf.ET.EXEC) { + @panic("loader: invalid ELF type (expected ET_EXEC)"); + } + + var phdr: *std.elf.Elf64.Phdr = @ptrFromInt(@intFromPtr(kernel) + elf.phoff); + + for (0..elf.phnum) |_| { + switch (phdr.type) { + .LOAD => { + r.mem.pagemap.map_range_allocating(phdr.vaddr, std.mem.alignForward(usize, phdr.memsz, r.page_size), .{ + .execute = phdr.flags.X, + // Gotta have RW access for loading it. + // XXX remap the pages and set protections properly... + .read = true, + .write = true, + .global = true, + }); + + const off: [*]u8 = @ptrFromInt(@intFromPtr(kernel) + phdr.offset); + const source = off[0..phdr.filesz]; + const va: [*]u8 = @ptrFromInt(phdr.vaddr); + const dest = va[0..phdr.filesz]; + + // Copy the file data. + @memcpy(dest, source); + + // Fill the rest of memsz with 0. + const zero_dest = va[phdr.filesz..phdr.memsz]; + @memset(zero_dest, 0); + }, + else => {}, + } + + phdr = @ptrFromInt(@intFromPtr(phdr) + elf.phentsize); + } + + return elf.entry; +} + +extern fn jump_to_kernel(loader_info: usize, stack: usize, entry: usize) void; + +pub fn loader_main(kernel: *anyopaque) void { + r.mem.init(); + const entry = load_kernel(kernel); + const stack = r.mem.alloc(1024 * 32); + + std.log.info("jumping to {x} with stack {x}", .{ entry, stack }); + jump_to_kernel(@intFromPtr(&r.loader_info), stack + (1024 * 32), entry); +} diff --git a/src/loader/mem.zig b/src/loader/mem.zig new file mode 100644 index 0000000..f875823 --- /dev/null +++ b/src/loader/mem.zig @@ -0,0 +1,246 @@ +const r = @import("root"); +const config = @import("config"); +const pmap = @import("pmap.zig"); + +var memory_map: *r.BootInfo.MemMap = &r.loader_info.memory_map; +var alloc_idx: usize = 0; +var bump_base: usize = 0; +var map_loaded: bool = false; + +const MemoryLayout = struct { + /// Kernel direct map (if applicable) + direct_map: usize, + /// Kernel heap + kernel_heap: usize, + /// PFNDB + pfndb: usize, + /// Loader heap data (per-CPU data, stacks, etc.) + loader_heap: usize, +}; + +pub const MapFlags = packed struct { + read: bool = true, + write: bool = false, + user: bool = false, + execute: bool = false, + global: bool = false, + write_through: bool = false, + cache_disable: bool = false, +}; + +pub var memory_layout: MemoryLayout = undefined; + +pub var pagemap: pmap.PMap = undefined; + +pub fn p2v(pa: usize) usize { + if (map_loaded) { + return memory_layout.direct_map + pa; + } + + return r.impl.p2v(pa); +} + +pub fn alloc_page() usize { + while (alloc_idx < memory_map.entry_count) { + const entry = &memory_map.entries[alloc_idx]; + + if (entry.type != .Free or entry.size < r.page_size) { + alloc_idx += 1; + continue; + } + + const addr = entry.base; + + entry.base += r.page_size; + entry.size -= r.page_size; + + const a: [*]u64 = @ptrFromInt(p2v(addr)); + + @memset(a[0..512], 0); + + return addr; + } + + @panic("loader: Out of memory for early allocation"); +} + +/// Allocate data from the loader heap. +pub fn alloc(size: usize) usize { + const va = bump_base; + bump_base += size; + + const npages = size / r.page_size; + + for (0..npages) |i| { + const pa = alloc_page(); + + pagemap.map_contiguous_range(va + i * r.page_size, pa, r.page_size, .{ + .read = true, + .write = true, + }); + } + + return va; +} + +pub fn add_entry(base: usize, size: usize, @"type": r.BootInfo.MemMap.Entry.Type) void { + memory_map.entries[memory_map.entry_count] = .{ + .base = base, + .size = size, + .type = @"type", + }; + + memory_map.entry_count += 1; +} + +extern var text_start_addr: u8; +extern var text_end_addr: u8; +extern var rodata_start_addr: u8; +extern var rodata_end_addr: u8; +extern var data_start_addr: u8; +extern var data_end_addr: u8; + +fn map_self() void { + const layout = r.impl.get_image_layout(); + + const text_start = @intFromPtr(&text_start_addr); + const text_end = @intFromPtr(&text_end_addr); + const rodata_start = @intFromPtr(&rodata_start_addr); + const rodata_end = @intFromPtr(&rodata_end_addr); + const data_start = @intFromPtr(&data_start_addr); + const data_end = @intFromPtr(&data_end_addr); + + const text_size = text_end - text_start; + const rodata_size = rodata_end - rodata_start; + const data_size = data_end - data_start; + + const start_phys = layout.physical_base; + const start_virt = layout.virtual_base; + const until_size = text_start - start_virt; + + pagemap.map_contiguous_range(start_virt, start_phys, until_size, .{ + .read = true, + .write = true, + .global = true, + }); + + pagemap.map_contiguous_range( + text_start, + layout.physical_base + (text_start - layout.virtual_base), + text_size, + .{ + .read = true, + .execute = true, + .global = true, + }, + ); + + pagemap.map_contiguous_range( + rodata_start, + layout.physical_base + (rodata_start - layout.virtual_base), + rodata_size, + .{ + .read = true, + .global = true, + }, + ); + + pagemap.map_contiguous_range( + data_start, + layout.physical_base + (data_start - layout.virtual_base), + data_size, + .{ + .read = true, + .write = true, + .global = true, + }, + ); + + const max_hhdm_address = 4 * 1024 * 1024 * 1024; + + pagemap.map_contiguous_range( + memory_layout.direct_map, + 0, + max_hhdm_address, + .{ + .read = true, + .write = true, + .global = true, + }, + ); + + // Now go through every usable entry and map to the HHDM every part that isnt covered by [0, hhdm_minimum_max_address). + // We can't blindly map until the maximum usable physical address because on some CPUs this might cause MCEs. + // See https://github.com/torvalds/linux/commit/66520ebc2df3fe52eb4792f8101fac573b766baf + for (0..memory_map.entry_count) |i| { + const entry = memory_map.entries[i]; + + if ((entry.type != .Free and entry.type != .LoaderReclaimable) or entry.size < r.page_size) { + continue; + } + + if (entry.base + entry.size <= max_hhdm_address) { + continue; + } + + var entry_start = entry.base; + var entry_size = entry.size; + + if (entry.base < max_hhdm_address) { + const adjust = max_hhdm_address - entry.base; + entry_start += adjust; + entry_size -= adjust; + } + + pagemap.map_contiguous_range( + memory_layout.direct_map + entry_start, + entry_start, + entry_size, + .{ + .read = true, + .write = true, + .global = true, + }, + ); + } +} + +pub fn init() void { + const vbits = r.arch.virtual_bits; + + // Figure out the memory layout. + // NOTE: This assumes 64-bit (fix this when we port to 32-bit). + // Higher half starts here, we give 1/2 to the kernel for its direct map. + const higher_half_start: usize = @bitCast(-@as(isize, 1) << vbits - 1); + const kernel_heap = higher_half_start + (@as(usize, 1) << vbits - 2); + + // Then, we give 16 TiB to the kernel heap. + const pfndb = kernel_heap + 0x100000000000; + + // Finally, the pfndb has 1 TiB. + const loader_heap = pfndb + 0x10000000000; + + memory_layout = .{ + .direct_map = higher_half_start, + .kernel_heap = kernel_heap, + .pfndb = pfndb, + .loader_heap = loader_heap, + }; + + bump_base = loader_heap; + + pagemap.root_pa = alloc_page(); + + // Pre-allocate the top 256 entries. + const table_ptr: [*]r.arch.Pte = @ptrFromInt(p2v(pagemap.root_pa)); + @memset(table_ptr[0..512], r.arch.Pte.zero()); + + for (256..512) |i| { + table_ptr[i] = r.arch.make_table_pte(alloc_page()); + } + + map_self(); + + map_loaded = true; + r.arch.activate(pagemap.root_pa); +} diff --git a/src/loader/pmap.zig b/src/loader/pmap.zig new file mode 100644 index 0000000..83bd521 --- /dev/null +++ b/src/loader/pmap.zig @@ -0,0 +1,238 @@ +const r = @import("root"); +const mem = r.mem; + +pub const PMapLevel = struct { + shift: u6, + mask: usize, + leaf: bool, +}; + +const MapItem = struct { + pa: usize, + len: usize, + flags: mem.MapFlags, +}; + +/// A mapping source that maps a single contiguous virtual-to-physical region. +/// All calls to `next` return the same physical base address and size, so this +/// is intended to be consumed exactly once by `map_from`. +const ContiguousSource = struct { + flags: mem.MapFlags, + size: usize, + base_va: usize, + base_pa: usize, + + pub fn next(self: *const ContiguousSource, _: usize) MapItem { + return .{ + .pa = self.base_pa, + .len = self.size, + .flags = self.flags, + }; + } +}; + +/// A mapping source that allocates physical pages on demand. Each call to `next` +/// returns a single page, so this is intended to be consumed by `map_from` in a loop until the entire range is mapped. +const AllocatingSource = struct { + flags: mem.MapFlags, + + pub fn next(self: *const AllocatingSource, _: usize) MapItem { + const pa = mem.alloc_page(); + return .{ + .pa = pa, + .len = r.page_size, + .flags = self.flags, + }; + } +}; + +fn level_shift(level: usize) u6 { + return r.arch.levels[level].shift; +} + +fn page_size(level: usize) usize { + return @as(usize, 1) << level_shift(level); +} + +fn index_for_level(va: usize, level: usize) usize { + return (va >> level_shift(level)) & r.arch.levels[level].mask; +} + +pub const PMap = struct { + const Self = @This(); + + const num_levels = r.arch.levels.len; + const entries_per_table = 512; + const index_mask = entries_per_table - 1; + + root_pa: usize, + + fn is_leaf_level(level: usize) bool { + return r.arch.levels[level].leaf; + } + + fn leaf_level_enabled(level: usize) bool { + return is_leaf_level(level) and r.arch.is_leaf_level_enabled(level); + } + + fn cursor(self: *Self, va: usize) Cursor { + var c = Cursor{ + .pmap = self, + .va = va, + .tables = undefined, + .top_level = num_levels - 1, + }; + + c.tables[num_levels - 1] = @ptrFromInt(r.mem.p2v(self.root_pa)); + return c; + } + + pub fn activate(self: *Self) void { + r.arch.activate(self.root_pa); + } + + pub fn map_from(self: *Self, va: usize, size: usize, source: anytype) void { + var c = self.cursor(va); + var remain = size; + + while (remain > 0) { + const item = source.next(c.va); + c.map_range(item.pa, item.len, item.flags); + remain -|= item.len; + } + } + + /// Map a contiguous virtual address range to a contiguous physical address range. + pub fn map_contiguous_range(self: *Self, va: usize, pa: usize, size: usize, flags: r.mem.MapFlags) void { + const src = ContiguousSource{ + .flags = flags, + .size = size, + .base_va = va, + .base_pa = pa, + }; + + self.map_from(va, size, src); + } + + /// Map a single virtual page to a physical page. + pub fn map_page(self: *Self, va: usize, pa: usize, flags: r.mem.MapFlags) void { + self.map_contiguous_range(va, pa, r.page_size, flags); + } + + /// Map a virtual address range to physical addresses + pub fn map_range_allocating(self: *Self, va: usize, size: usize, flags: mem.MapFlags) void { + const src = AllocatingSource{ + .flags = flags, + }; + + self.map_from(va, size, src); + } + + pub const Cursor = struct { + pmap: *Self, + va: usize, + /// Cached pointers to each level's page table, filled as the cursor + /// descends. Only indices in `[0, top_level]` are valid. + tables: [num_levels][*]r.arch.Pte, + /// Index of the highest level for which `tables` currently holds a + /// valid pointer. Descends toward 0 (the leaf) as `walk_down` runs. + /// Reset upward in `advance` when the VA crosses a level boundary. + top_level: usize, + + fn walk_down(self: *Cursor, target_level: usize, allocate: bool) error{PageNotMapped}!void { + var current_level = self.top_level; + + while (current_level > target_level) { + const idx = index_for_level(self.va, current_level); + var pte = r.arch.Pte.load(&self.tables[current_level][idx]); + + if (!pte.present) { + if (!allocate) { + return error.PageNotMapped; + } + + const new_table_pa = mem.alloc_page(); + + const table_ptr: [*]r.arch.Pte = @ptrFromInt(r.mem.p2v(new_table_pa)); + @memset(table_ptr[0..entries_per_table], r.arch.Pte.zero()); + + pte = r.arch.make_table_pte(new_table_pa); + self.tables[current_level][idx] = pte; + } + + const child_va = r.mem.p2v(pte.address()); + self.tables[current_level - 1] = @ptrFromInt(child_va); + self.top_level -= 1; + current_level -= 1; + } + } + + pub fn walk_down_resolve(self: *Cursor, target_level: usize) bool { + self.walk_down(target_level, false, false) catch return false; + return true; + } + + /// Choose the largest page size that satisfies the alignment and + /// size constraints of the current VA, PA, and remaining byte count. + /// Tries large pages first; falls back to 4K. + fn choose_target_level(self: *Cursor, pa: usize, remain: usize) usize { + var level: usize = num_levels; + while (level > 0) { + level -= 1; + if (!leaf_level_enabled(level)) continue; + + const pg_size = page_size(level); + const align_mask = pg_size - 1; + + if (remain >= pg_size and + (self.va & align_mask) == 0 and + (pa & align_mask) == 0) + { + return level; + } + } + + @panic("no leaf level can map current alignment/size"); + } + + pub fn map_range(self: *Cursor, pa: usize, size: usize, flags: mem.MapFlags) void { + var remain = size; + var current_pa = pa; + + while (remain > 0) { + const target_level = self.choose_target_level(current_pa, remain); + + self.walk_down(target_level, true) catch @panic("walk_down failed during map_range"); + + const table = self.tables[target_level]; + table[index_for_level(self.va, target_level)] = + r.arch.make_leaf_pte(current_pa, flags, target_level); + + const step = page_size(target_level); + current_pa += step; + remain -= step; + + self.advance(step); + } + } + + /// Advance the cursor's virtual address by `offset` bytes and + /// invalidate any cached table pointers that no longer apply. + /// The cursor tracks the highest level whose index is still valid + /// for the new VA, `walk_down` will re-descend from there on the + /// next mapping operation. + pub fn advance(self: *Cursor, offset: usize) void { + const old_va = self.va; + self.va +%= offset; + + var level: usize = num_levels - 1; + while (level > 0) : (level -= 1) { + const shift = level_shift(level); + if ((old_va >> shift) != (self.va >> shift)) { + self.top_level = level; + return; + } + } + } + }; +}; diff --git a/src/loader/private.zig b/src/loader/private.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/loader/root.zig b/src/loader/root.zig new file mode 100644 index 0000000..1a81c67 --- /dev/null +++ b/src/loader/root.zig @@ -0,0 +1,113 @@ +const rtl = @import("rtl"); +const config = @import("config"); +const std = @import("std"); + +pub const SystemInfo = struct { + ncpus: usize, +}; + +pub const BootInfo = @import("info.zig"); + +const DebugWriter = struct { + interface: std.Io.Writer, + + pub fn init() DebugWriter { + return .{ + .interface = .{ + .vtable = &.{ .drain = drain }, + .buffer = &.{}, + .end = 0, + }, + }; + } + + fn drain(_: *std.Io.Writer, data: []const []const u8, _: usize) std.Io.Writer.Error!usize { + var total_written: usize = 0; + for (data) |slice| { + for (slice) |byte| { + arch.debug_write(byte); + } + total_written += slice.len; + } + + return total_written; + } +}; + +pub fn log( + comptime level: std.log.Level, + comptime scope: @EnumLiteral(), + comptime fmt: []const u8, + args: anytype, +) void { + _ = level; + + const scope_str = if (scope == .default) "" else @tagName(scope) ++ ": "; + + // Calculate the length required. + var buf: [256]u8 = undefined; + + const written = std.fmt.bufPrint(&buf, scope_str ++ fmt ++ "\n", args) catch return; + + var writer = DebugWriter.init(); + + _ = writer.interface.writeAll(written) catch return; +} + +pub const std_options = std.Options{ + .logFn = log, +}; + +comptime { + _ = @import("limine/impl.zig"); + //_ = &limine; +} + +pub const page_size = 0x1000; + +pub const ImageLayout = struct { + physical_base: usize, + virtual_base: usize, +}; + +pub const ImplSchema = struct { + pub fn get_image_layout() ImageLayout { + return undefined; + } + + pub fn p2v(pa: usize) usize { + return pa; + } +}; + +pub const arch = switch (config.arch) { + .amd64 => @import("amd64/impl.zig"), + else => @compileError("unsupported architecture"), +}; + +pub const impl = switch (config.bootloader) { + .limine => @import("limine/impl.zig"), +}; + +comptime { + _ = rtl.assert_interface(impl, ImplSchema); +} + +pub const main = @import("main.zig").loader_main; + +pub const mem = @import("mem.zig"); + +pub var loader_info: BootInfo = std.mem.zeroes(BootInfo); + +fn _panic( + msg: []const u8, + _: ?usize, +) noreturn { + std.log.err("loader panic: {s}", .{msg}); + + while (true) { + asm volatile (""); + } +} + +pub const panic = std.debug.FullPanic(_panic); diff --git a/src/mm/amd64/impl.zig b/src/mm/amd64/impl.zig index 46b62fa..e4355f7 100644 --- a/src/mm/amd64/impl.zig +++ b/src/mm/amd64/impl.zig @@ -117,11 +117,5 @@ pub fn is_leaf_level_enabled(level: usize) bool { } pub fn init_kernel() void { - mi.kernel_space.pmap.root_pa = mi.phys.early_alloc(); - const table_ptr: [*]Pte = @ptrFromInt(mm.p2v(mi.kernel_space.pmap.root_pa)); - @memset(table_ptr[0..512], Pte.zero()); - - for (256..512) |i| { - table_ptr[i] = make_table_pte(mi.phys.early_alloc()); - } + mi.kernel_space.pmap.root_pa = amd64.read_cr(3); } diff --git a/src/mm/init.zig b/src/mm/init.zig index 8c5e805..384e8ce 100644 --- a/src/mm/init.zig +++ b/src/mm/init.zig @@ -4,106 +4,8 @@ const mm = r.mm; const mi = mm.private; const config = @import("config"); -extern var text_start_addr: u8; -extern var text_end_addr: u8; -extern var rodata_start_addr: u8; -extern var rodata_end_addr: u8; -extern var data_start_addr: u8; -extern var data_end_addr: u8; - -fn map_kernel(boot_info: *pl.BootInfo) void { - const kaddr = boot_info.kernel_address; - - const text_start = @intFromPtr(&text_start_addr); - const text_end = @intFromPtr(&text_end_addr); - const rodata_start = @intFromPtr(&rodata_start_addr); - const rodata_end = @intFromPtr(&rodata_end_addr); - const data_start = @intFromPtr(&data_start_addr); - const data_end = @intFromPtr(&data_end_addr); - - const text_size = text_end - text_start; - const rodata_size = rodata_end - rodata_start; - const data_size = data_end - data_start; - - const start_phys = kaddr.physical_base; - const start_virt = kaddr.virtual_base; - const until_size = text_start - start_virt; - - mi.kernel_space.pmap.map_contiguous_range(start_virt, start_phys, until_size, .{ - .read = true, - .write = true, - .global = true, - }, .Boot); - - mi.kernel_space.pmap.map_contiguous_range(text_start, kaddr.physical_base + (text_start - kaddr.virtual_base), text_size, .{ - .read = true, - .execute = true, - .global = true, - }, .Boot); - - mi.kernel_space.pmap.map_contiguous_range(rodata_start, kaddr.physical_base + (rodata_start - kaddr.virtual_base), rodata_size, .{ - .read = true, - .global = true, - }, .Boot); - - mi.kernel_space.pmap.map_contiguous_range(data_start, kaddr.physical_base + (data_start - kaddr.virtual_base), data_size, .{ - .read = true, - .write = true, - .global = true, - }, .Boot); - - mi.kernel_space.pmap.map_contiguous_range( - mm.p2v(0), - 0, - mi.impl.hhdm_minimum_max_address, - .{ - .read = true, - .write = true, - .global = true, - }, - .Boot, - ); - - // Now go through every usable entry and map to the HHDM every part that isnt covered by [0, hhdm_minimum_max_address). - // We can't blindly map until the maximum usable physical address because on some CPUs this might cause MCEs. - // See https://github.com/torvalds/linux/commit/66520ebc2df3fe52eb4792f8101fac573b766baf - for (0..boot_info.memory_map.entry_count) |i| { - const entry = boot_info.memory_map.entries[i]; - - if ((entry.type != .Free and entry.type != .LoaderReclaimable) or entry.size < mm.page_size) { - continue; - } - - if (entry.base + entry.size <= mi.impl.hhdm_minimum_max_address) { - continue; - } - - var entry_start = entry.base; - var entry_size = entry.size; - - if (entry.base < mi.impl.hhdm_minimum_max_address) { - const adjust = mi.impl.hhdm_minimum_max_address - entry.base; - entry_start += adjust; - entry_size -= adjust; - } - - mi.kernel_space.pmap.map_contiguous_range( - mm.p2v(entry_start), - entry_start, - entry_size, - .{ - .read = true, - .write = true, - .global = true, - }, - .Boot, - ); - } -} - -pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { +pub fn init(boot_info: *r.BootInfo) linksection(r.init) void { mi.phys.init(boot_info); - map_kernel(boot_info); mi.kernel_space.pmap.activate(); mi.phys.init_pfndb(); mi.zone.early_init(); diff --git a/src/mm/phys.zig b/src/mm/phys.zig index d586787..ec9e2cd 100644 --- a/src/mm/phys.zig +++ b/src/mm/phys.zig @@ -20,7 +20,7 @@ pub var high_memory_threshold: usize = 0; var bootstrapped = false; var early_alloc_entry_idx: usize = 0; -var memory_map: *pl.BootInfo.MemMap = undefined; +var memory_map: *r.BootInfo.MemMap = undefined; var early_allocs: usize = 0; var list_lock: ke.SpinLock = .init(); var free_list: rtl.List = undefined; @@ -162,7 +162,7 @@ pub fn free_batch(head: *mm.Page, tail: *mm.Page, count: usize) void { list_lock.release(ipl); } -pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { +pub fn init(boot_info: *r.BootInfo) linksection(r.init) void { memory_map = &boot_info.memory_map; free_list.init(); free_page_event.init(.Notification); diff --git a/src/mm/zone.zig b/src/mm/zone.zig index 0086805..ff6b47b 100644 --- a/src/mm/zone.zig +++ b/src/mm/zone.zig @@ -416,7 +416,7 @@ inline fn wma_mix(old: usize, new: usize) usize { return (3 * old + new * wma_unit) / 4; } -fn alloc_cpus() []rtl.CachePadded(Cpu) { +fn cpus_alloc() []rtl.CachePadded(Cpu) { const ptr = cpu_zone.alloc(.{}) catch @panic("failed to allocate zone CPU state"); const raw: [*]rtl.CachePadded(Cpu) = @ptrCast(@alignCast(ptr)); return raw[0..ke.ncpus]; @@ -526,7 +526,7 @@ pub const Zone = struct { return; } - self.cpus = alloc_cpus(); + self.cpus = cpus_alloc(); for (0..ke.ncpus) |i| { self.cpus[i].value.lock = .init(); @@ -1284,7 +1284,7 @@ pub fn late_init() linksection(r.init) void { while (zone) |z| { if (z.use_magazines) { - z.cpus = alloc_cpus(); + z.cpus = cpus_alloc(); for (0..ke.ncpus) |i| { z.cpus[i] = .init(.{ diff --git a/src/platform/acpi/root.zig b/src/platform/acpi/root.zig index 120c826..15d05d3 100644 --- a/src/platform/acpi/root.zig +++ b/src/platform/acpi/root.zig @@ -322,7 +322,7 @@ fn enumerate_tables() void { } } -pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { +pub fn init(boot_info: *r.BootInfo) linksection(r.init) void { const rsdp_addr = boot_info.rsdp orelse return; const rsdp: *Rsdp = @ptrFromInt(rsdp_addr); diff --git a/src/platform/pc/impl.zig b/src/platform/pc/impl.zig index bac6276..51db3a2 100644 --- a/src/platform/pc/impl.zig +++ b/src/platform/pc/impl.zig @@ -32,7 +32,7 @@ fn com1_write(c: u8) void { amd64.outb(com1, c); } -pub fn early_init(boot_info: *pl.BootInfo) linksection(r.init) void { +pub fn early_init(boot_info: *r.BootInfo) linksection(r.init) void { com1_init(); const f = amd64.cpu_features; @@ -79,7 +79,7 @@ pub fn init_ap() void { apic.init_local(); } -pub fn late_init(boot_info: *pl.BootInfo) linksection(r.init) void { +pub fn late_init(boot_info: *r.BootInfo) linksection(r.init) void { if (amd64.hypervisor.info) |hv| { init_hypervisor(hv); } @@ -87,6 +87,7 @@ pub fn late_init(boot_info: *pl.BootInfo) linksection(r.init) void { pl.acpi.init(boot_info); tsc.init(); + apic.init(); if (apic.apics.items.len != 0) { diff --git a/src/platform/root.zig b/src/platform/root.zig index 0ad0817..0977631 100644 --- a/src/platform/root.zig +++ b/src/platform/root.zig @@ -2,6 +2,7 @@ //! To add a new platform, create a new file and implement all required functions. const config = @import("config"); +const r = @import("root"); pub const impl = switch (config.arch) { .amd64 => @import("pc/impl.zig"), @@ -11,13 +12,13 @@ pub const impl = switch (config.arch) { pub const acpi = @import("acpi/root.zig"); /// Early platform-specific initialization, called before anything else. -pub inline fn early_init(boot_info: *BootInfo) void { +pub inline fn early_init(boot_info: *r.BootInfo) void { return impl.early_init(boot_info); } /// Late platform init. Memory allocator is available at this point. /// SMP bringup must be done after this returns. -pub inline fn late_init(boot_info: *BootInfo) void { +pub inline fn late_init(boot_info: *r.BootInfo) void { return impl.late_init(boot_info); } @@ -38,53 +39,3 @@ pub inline fn arm_timer(ns: u64) void { /// Pretty name for the platform. pub const name = impl.name; - -/// A generic struct representing information passed from the bootloader. -pub const BootInfo = struct { - /// Represents the system memory map. - pub const MemMap = struct { - /// A memory map entry. - pub const Entry = struct { - pub const Type = enum { - Free, - Reserved, - LoaderReclaimable, - AcpiNvs, - AcpiReclaimable, - Kernel, - }; - - type: Type, - base: usize, - size: usize, - }; - - entry_count: usize, - entries: [256]Entry, - }; - - pub const Framebuffer = struct { - address: usize, - width: u32, - height: u32, - pitch: u32, - bpp: u8, - }; - - pub const KernelAddress = struct { physical_base: usize, virtual_base: usize }; - - /// RSDP on ACPI machines - rsdp: ?usize, - - /// Kernel command-line arguments - cmdline: ?[]const u8, - - /// Memory map - memory_map: MemMap, - - /// Framebuffer info, if available - framebuffer: ?Framebuffer, - - /// Kernel address - kernel_address: KernelAddress, -}; diff --git a/src/root.zig b/src/root.zig index c562503..85593be 100644 --- a/src/root.zig +++ b/src/root.zig @@ -7,6 +7,7 @@ pub const ksyms = @import("ksyms"); pub const arch = @import("arch"); pub const mm = @import("mm/root.zig"); pub const ps = @import("proc/root.zig"); +pub const BootInfo = @import("loader/info.zig"); const config = @import("config"); @@ -59,5 +60,5 @@ pub const panic = std.debug.FullPanic(ke.panic); comptime { // Ensure the entry point is linked in. - _ = @import("boot/limine/entry.zig"); + _ = @import("kern/entry.zig"); }