loader: initial loader code

This commit is contained in:
Loïc Grégoire
2026-07-16 19:48:01 -04:00
parent 26ec72fb36
commit a79c7a07cb
35 changed files with 1197 additions and 548 deletions
+66 -3
View File
@@ -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); const kernel = addKernel(b, plat, optimize, config_module, ksyms_module, rtl_module, arch_module);
kernel.root_module.addAssemblyFile(ksyms_s); 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); b.default_step.dependOn(&b.addInstallArtifact(kernel, .{}).step);
const docs_module = b.createModule(.{ const docs_module = b.createModule(.{
@@ -108,10 +111,10 @@ pub fn build(b: *std.Build) void {
docs_step.dependOn(&install_docs.step); docs_step.dependOn(&install_docs.step);
const iso_step = b.step("iso", "Build the ISO image"); 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); iso_step.dependOn(result.step);
run.addRun(b, kernel, plat); run.addRun(b, kernel, loader, plat);
const rtl_tests = b.addTest(.{ const rtl_tests = b.addTest(.{
.root_module = b.createModule(.{ .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("ksyms", ksyms_module);
kernel.root_module.addImport("rtl", rtl); kernel.root_module.addImport("rtl", rtl);
kernel.link_gc_sections = false;
kernel.use_llvm = true; kernel.use_llvm = true;
kernel.use_lld = true; kernel.use_lld = true;
@@ -215,7 +220,65 @@ fn addKernel(b: *std.Build, plat: config.Platform, optimize: std.builtin.Optimiz
else => {}, 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; 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;
}
+1 -2
View File
@@ -9,8 +9,7 @@ pub const Bootloader = enum {
pub const Platform = struct { pub const Platform = struct {
arch: std.Target.Cpu.Arch, arch: std.Target.Cpu.Arch,
os: std.Target.Os.Tag, os: std.Target.Os.Tag,
bootloader: Bootloader = .Limine,
bootloader: ?Bootloader = null,
}; };
const ArchEnum = enum { const ArchEnum = enum {
+9 -4
View File
@@ -21,20 +21,24 @@ pub fn addIso(
arch: Arch, arch: Arch,
image_name: []const u8, image_name: []const u8,
kernel: *std.Build.Step.Compile, kernel: *std.Build.Step.Compile,
loader: *std.Build.Step.Compile,
limine: *std.Build.Dependency, limine: *std.Build.Dependency,
) IsoResult { ) IsoResult {
const limine_tool = addLimineTool(b, limine); const limine_tool = addLimineTool(b, limine);
const kernel_bin = kernel.getEmittedBin(); const kernel_bin = kernel.getEmittedBin();
const loader_bin = loader.getEmittedBin();
const prep_script = const prep_script =
\\set -euo pipefail \\set -euo pipefail
\\kernel="$1" \\kernel="$1"
\\iso_root="$2" \\loader="$2"
\\limine_conf="$3" \\iso_root="$3"
\\limine_dir="$4" \\limine_conf="$4"
\\arch="$5" \\limine_dir="$5"
\\arch="$6"
\\rm -rf "$iso_root" \\rm -rf "$iso_root"
\\mkdir -p "$iso_root/boot/limine" "$iso_root/EFI/BOOT" \\mkdir -p "$iso_root/boot/limine" "$iso_root/EFI/BOOT"
\\cp "$loader" "$iso_root/boot/loader"
\\cp "$kernel" "$iso_root/boot/kernel" \\cp "$kernel" "$iso_root/boot/kernel"
\\cp "$limine_conf" "$iso_root/boot/limine/limine.conf" \\cp "$limine_conf" "$iso_root/boot/limine/limine.conf"
\\case "$arch" in \\case "$arch" in
@@ -62,6 +66,7 @@ pub fn addIso(
const prep = b.addSystemCommand(&.{ "bash", "-ceu", prep_script, "--" }); const prep = b.addSystemCommand(&.{ "bash", "-ceu", prep_script, "--" });
prep.addFileArg(kernel_bin); prep.addFileArg(kernel_bin);
prep.addFileArg(loader_bin);
prep.addDirectoryArg(iso_root.getDirectory()); prep.addDirectoryArg(iso_root.getDirectory());
prep.addFileArg(b.path("build/limine.conf")); prep.addFileArg(b.path("build/limine.conf"));
prep.addDirectoryArg(limine.path(".")); prep.addDirectoryArg(limine.path("."));
+4 -1
View File
@@ -7,5 +7,8 @@ timeout: 3
protocol: limine protocol: limine
# Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. # 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
-75
View File
@@ -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.*)
}
}
@@ -1,12 +1,7 @@
/* Tell the linker that we want an x86_64 ELF64 output file */
OUTPUT_FORMAT(elf64-x86-64) OUTPUT_FORMAT(elf64-x86-64)
/* We want the symbol kmain to be our entry point */
ENTRY(kmain) 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 PHDRS
{ {
limine_requests PT_LOAD; limine_requests PT_LOAD;
@@ -17,22 +12,20 @@ PHDRS
SECTIONS 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; . = 0xffffffff80000000;
/* Define a section to contain the Limine requests and assign it to its own PHDR */
.limine_requests : { .limine_requests : {
KEEP(*(.limine_requests_start)) KEEP(*(.limine_requests_start))
KEEP(*(.limine_requests)) KEEP(*(.limine_requests))
KEEP(*(.limine_requests_end)) KEEP(*(.limine_requests_end))
} :limine_requests } :limine_requests
/* Move to the next memory page for .text */
. = ALIGN(CONSTANT(MAXPAGESIZE)); . = ALIGN(CONSTANT(MAXPAGESIZE));
text_start_addr = .; text_start_addr = .;
.text : { .text : {
@@ -40,11 +33,9 @@ SECTIONS
} :text } :text
/* Move to the next memory page for .rodata */
. = ALIGN(CONSTANT(MAXPAGESIZE)); . = ALIGN(CONSTANT(MAXPAGESIZE));
text_end_addr = .; text_end_addr = .;
rodata_start_addr = .; rodata_start_addr = .;
.rodata : { .rodata : {
@@ -52,14 +43,11 @@ SECTIONS
} :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 : {
*(.note.gnu.build-id) *(.note.gnu.build-id)
} :rodata } :rodata
/* Move to the next memory page for .data */
. = ALIGN(CONSTANT(MAXPAGESIZE)); . = ALIGN(CONSTANT(MAXPAGESIZE));
rodata_end_addr = .; rodata_end_addr = .;
@@ -83,10 +71,7 @@ SECTIONS
} :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 .bss.*) *(.bss .bss.*)
*(COMMON) *(COMMON)
@@ -97,7 +82,6 @@ SECTIONS
data_end_addr = .; data_end_addr = .;
/* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
/DISCARD/ : { /DISCARD/ : {
*(.eh_frame*) *(.eh_frame*)
*(.note .note.*) *(.note .note.*)
+68
View File
@@ -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.*)
}
}
-75
View File
@@ -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.*)
}
}
-75
View File
@@ -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.*)
}
}
-17
View File
@@ -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));
+4 -4
View File
@@ -8,7 +8,7 @@ const QemuArgs = struct {
args: [][]u8, 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 run_step = b.step("run", "Run the kernel");
const uefi = b.option(bool, "uefi", "Boot with UEFI firmware") orelse false; const uefi = b.option(bool, "uefi", "Boot with UEFI firmware") orelse false;
const debug = b.option(bool, "debug", "Boot in debug mode") 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) { if (plat.os == .freestanding) {
const limine = b.dependency("limine", .{}); const limine = b.dependency("limine", .{});
const ovmf = b.dependency("ovmf", .{}); 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) { const qemu_bin = switch (plat.arch) {
.x86_64 => "qemu-system-x86_64", .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(&.{ qemu.addArgs(&.{
"-d", "-d",
"int", "int",
"-s", //"-s",
"-S", //"-S",
"-D", "-D",
"qemu.log", "qemu.log",
"-no-reboot", "-no-reboot",
-100
View File
@@ -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);
}
+1 -1
View File
@@ -217,7 +217,7 @@ fn explodeable(_: ?*anyopaque) void {
} }
pub fn start(param: ?*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(); rand_gen ^= rand_tsc_based();
+1 -1
View File
@@ -11,7 +11,7 @@ const rtl = @import("rtl");
// XXX // XXX
const fbconsole = @import("../dev/fbconsole.zig"); 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); mm.init(boot_info);
ps.init(); ps.init();
+16
View File
@@ -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);
}
+1 -1
View File
@@ -9,7 +9,7 @@ const ex = r.ex;
var thread0: ke.Thread = undefined; var thread0: ke.Thread = undefined;
var stack: [r.kib(16)]u8 align(16) = 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.impl.early_init();
ki.tunable.init(boot_info); ki.tunable.init(boot_info);
std.log.info("Welcome to the machine", .{}); std.log.info("Welcome to the machine", .{});
+1 -1
View File
@@ -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 cmdline = boot_info.cmdline orelse return;
const elems = set.elems(); const elems = set.elems();
+108
View File
@@ -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);
}
+45
View File
@@ -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,
+5
View File
@@ -0,0 +1,5 @@
.globl jump_to_kernel
jump_to_kernel:
mov %rsi, %rsp
push $0
jmp *%rdx ; rdi = loader_info
+117
View File
@@ -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.?);
}
@@ -155,3 +155,71 @@ pub const Framebuffer = extern struct {
edid_size: u64, edid_size: u64,
edid: ?*anyopaque, 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,
};
+64
View File
@@ -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);
}
+246
View File
@@ -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);
}
+238
View File
@@ -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;
}
}
}
};
};
View File
+113
View File
@@ -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);
+1 -7
View File
@@ -117,11 +117,5 @@ pub fn is_leaf_level_enabled(level: usize) bool {
} }
pub fn init_kernel() void { pub fn init_kernel() void {
mi.kernel_space.pmap.root_pa = mi.phys.early_alloc(); mi.kernel_space.pmap.root_pa = amd64.read_cr(3);
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());
}
} }
+1 -99
View File
@@ -4,106 +4,8 @@ const mm = r.mm;
const mi = mm.private; const mi = mm.private;
const config = @import("config"); const config = @import("config");
extern var text_start_addr: u8; pub fn init(boot_info: *r.BootInfo) linksection(r.init) void {
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 {
mi.phys.init(boot_info); mi.phys.init(boot_info);
map_kernel(boot_info);
mi.kernel_space.pmap.activate(); mi.kernel_space.pmap.activate();
mi.phys.init_pfndb(); mi.phys.init_pfndb();
mi.zone.early_init(); mi.zone.early_init();
+2 -2
View File
@@ -20,7 +20,7 @@ pub var high_memory_threshold: usize = 0;
var bootstrapped = false; var bootstrapped = false;
var early_alloc_entry_idx: usize = 0; 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 early_allocs: usize = 0;
var list_lock: ke.SpinLock = .init(); var list_lock: ke.SpinLock = .init();
var free_list: rtl.List = undefined; 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); 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; memory_map = &boot_info.memory_map;
free_list.init(); free_list.init();
free_page_event.init(.Notification); free_page_event.init(.Notification);
+3 -3
View File
@@ -416,7 +416,7 @@ inline fn wma_mix(old: usize, new: usize) usize {
return (3 * old + new * wma_unit) / 4; 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 ptr = cpu_zone.alloc(.{}) catch @panic("failed to allocate zone CPU state");
const raw: [*]rtl.CachePadded(Cpu) = @ptrCast(@alignCast(ptr)); const raw: [*]rtl.CachePadded(Cpu) = @ptrCast(@alignCast(ptr));
return raw[0..ke.ncpus]; return raw[0..ke.ncpus];
@@ -526,7 +526,7 @@ pub const Zone = struct {
return; return;
} }
self.cpus = alloc_cpus(); self.cpus = cpus_alloc();
for (0..ke.ncpus) |i| { for (0..ke.ncpus) |i| {
self.cpus[i].value.lock = .init(); self.cpus[i].value.lock = .init();
@@ -1284,7 +1284,7 @@ pub fn late_init() linksection(r.init) void {
while (zone) |z| { while (zone) |z| {
if (z.use_magazines) { if (z.use_magazines) {
z.cpus = alloc_cpus(); z.cpus = cpus_alloc();
for (0..ke.ncpus) |i| { for (0..ke.ncpus) |i| {
z.cpus[i] = .init(.{ z.cpus[i] = .init(.{
+1 -1
View File
@@ -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_addr = boot_info.rsdp orelse return;
const rsdp: *Rsdp = @ptrFromInt(rsdp_addr); const rsdp: *Rsdp = @ptrFromInt(rsdp_addr);
+3 -2
View File
@@ -32,7 +32,7 @@ fn com1_write(c: u8) void {
amd64.outb(com1, c); 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(); com1_init();
const f = amd64.cpu_features; const f = amd64.cpu_features;
@@ -79,7 +79,7 @@ pub fn init_ap() void {
apic.init_local(); 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| { if (amd64.hypervisor.info) |hv| {
init_hypervisor(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); pl.acpi.init(boot_info);
tsc.init(); tsc.init();
apic.init(); apic.init();
if (apic.apics.items.len != 0) { if (apic.apics.items.len != 0) {
+3 -52
View File
@@ -2,6 +2,7 @@
//! To add a new platform, create a new file and implement all required functions. //! To add a new platform, create a new file and implement all required functions.
const config = @import("config"); const config = @import("config");
const r = @import("root");
pub const impl = switch (config.arch) { pub const impl = switch (config.arch) {
.amd64 => @import("pc/impl.zig"), .amd64 => @import("pc/impl.zig"),
@@ -11,13 +12,13 @@ pub const impl = switch (config.arch) {
pub const acpi = @import("acpi/root.zig"); pub const acpi = @import("acpi/root.zig");
/// Early platform-specific initialization, called before anything else. /// 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); return impl.early_init(boot_info);
} }
/// Late platform init. Memory allocator is available at this point. /// Late platform init. Memory allocator is available at this point.
/// SMP bringup must be done after this returns. /// 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); return impl.late_init(boot_info);
} }
@@ -38,53 +39,3 @@ pub inline fn arm_timer(ns: u64) void {
/// Pretty name for the platform. /// Pretty name for the platform.
pub const name = impl.name; 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,
};
+2 -1
View File
@@ -7,6 +7,7 @@ pub const ksyms = @import("ksyms");
pub const arch = @import("arch"); pub const arch = @import("arch");
pub const mm = @import("mm/root.zig"); pub const mm = @import("mm/root.zig");
pub const ps = @import("proc/root.zig"); pub const ps = @import("proc/root.zig");
pub const BootInfo = @import("loader/info.zig");
const config = @import("config"); const config = @import("config");
@@ -59,5 +60,5 @@ pub const panic = std.debug.FullPanic(ke.panic);
comptime { comptime {
// Ensure the entry point is linked in. // Ensure the entry point is linked in.
_ = @import("boot/limine/entry.zig"); _ = @import("kern/entry.zig");
} }