From dc6f7e486f226e427cf3fe878569580abafe2bb7 Mon Sep 17 00:00:00 2001 From: ktkk Date: Tue, 15 Apr 2025 13:41:59 +0000 Subject: [PATCH 1/4] Initial commit --- .envrc | 1 + .gitignore | 9 +++ build.zig | 42 ++++++++++++++ flake.lock | 61 ++++++++++++++++++++ flake.nix | 43 +++++++++++++++ kernel.ld | 27 +++++++++ src/Console.zig | 135 +++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 89 ++++++++++++++++++++++++++++++ src/utils/Lazy.zig | 20 +++++++ 9 files changed, 427 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 kernel.ld create mode 100644 src/Console.zig create mode 100644 src/main.zig create mode 100644 src/utils/Lazy.zig diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..8392d15 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d4506d --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Zig +zig-* +.zig-* + +# Nix +result* + +# Misc +.direnv diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..05ff4db --- /dev/null +++ b/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + var disabled_features = std.Target.Cpu.Feature.Set.empty; + var enabled_features = std.Target.Cpu.Feature.Set.empty; + + disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.mmx)); + disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.sse)); + disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.sse2)); + disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.avx)); + disabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.avx2)); + + enabled_features.addFeature(@intFromEnum(std.Target.x86.Feature.soft_float)); + + const target = b.resolveTargetQuery(.{ + .cpu_arch = std.Target.Cpu.Arch.x86, + .abi = std.Target.Abi.none, + .os_tag = std.Target.Os.Tag.freestanding, + .cpu_features_sub = disabled_features, + .cpu_features_add = enabled_features, + }); + + const kernel = b.addExecutable(.{ + .name = "kernel.elf", + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .ReleaseFast, + .code_model = .kernel, + }); + kernel.setLinkerScript(b.path("kernel.ld")); + + b.installArtifact(kernel); + + const qemu = b.addSystemCommand(&[_][]const u8{ + "qemu-system-x86_64", + }); + qemu.addArg("-kernel"); + qemu.addFileArg(kernel.getEmittedBin()); + + const run = b.step("run", "Run QEMU"); + run.dependOn(&qemu.step); +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..2272d3d --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1742669843, + "narHash": "sha256-G5n+FOXLXcRx+3hCJ6Rt6ZQyF1zqQ0DL0sWAMn2Nk0w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1e5b653dff12029333a6546c11e108ede13052eb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..902e1e9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,43 @@ +{ + description = "Zig Template"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = nixpkgs.legacyPackages.${system}; + nativeBuildInputs = with pkgs; [ + zig + zls + ]; + + buildInputs = with pkgs; [ + qemu + grub2 + ]; + in { + devShells.default = pkgs.mkShell {inherit nativeBuildInputs buildInputs;}; + + packages.default = pkgs.stdenv.mkDerivation { + pname = "template"; + version = "0.0.0"; + src = ./.; + + nativeBuildInputs = + nativeBuildInputs + ++ [ + pkgs.zig.hook + ]; + inherit buildInputs; + }; + } + ); +} diff --git a/kernel.ld b/kernel.ld new file mode 100644 index 0000000..447c7ed --- /dev/null +++ b/kernel.ld @@ -0,0 +1,27 @@ +ENTRY(_start) + +SECTIONS { + . = 1M; + + .multiboot : ALIGN(4K) { + KEEP(*(.multiboot)) + } + + .text : ALIGN(4K) { + *(.text) + } + + .rodata : ALIGN(4K) { + *(.rodata) + } + + .data : ALIGN(4K) { + *(.data); + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss) + } +} + diff --git a/src/Console.zig b/src/Console.zig new file mode 100644 index 0000000..e7bda52 --- /dev/null +++ b/src/Console.zig @@ -0,0 +1,135 @@ +const std = @import("std"); + +const Self = @This(); + +pub fn init() Self { + var self = Self{}; + self.clear(); + return self; +} + +const vga_width = 80; +const vga_height = 25; +const vga_size = vga_width * vga_height; + +pub const ConsoleColor = enum(u8) { + black = 0, + blue = 1, + green = 2, + cyan = 3, + red = 4, + magenta = 5, + brown = 6, + lightGray = 7, + darkGray = 8, + lightBlue = 9, + lightGreen = 10, + lightCyan = 11, + lightRed = 12, + lightMagenta = 13, + lightBrown = 14, + white = 15, +}; + +const default_color = vgaEntryColor(ConsoleColor.lightGray, ConsoleColor.black); + +column: usize = 0, +color: u8 = default_color, +buffer: [*]volatile u16 = @ptrFromInt(0xb8000), + +fn vgaEntryColor(fg: ConsoleColor, bg: ConsoleColor) u8 { + return @intFromEnum(fg) | (@intFromEnum(bg) << 4); +} + +fn vgaEntry(uc: u8, new_color: u8) u16 { + const c: u16 = new_color; + return uc | (c << 8); +} + +fn index(x: usize, y: usize) usize { + return y * vga_width + x; +} + +pub fn setColor(self: *Self, fg: ConsoleColor, bg: ConsoleColor) void { + self.color = vgaEntryColor(fg, bg); +} + +pub fn resetColor(self: *Self) void { + self.color = default_color; +} + +pub fn clear(self: *Self) void { + @memset(self.buffer[0..vga_size], vgaEntry(' ', self.color)); +} + +fn clearRow(self: *Self, y: usize) void { + for (0..vga_width) |x| { + const i = index(x, y); + self.buffer[i] = vgaEntry(' ', self.color); + } +} + +fn newLine(self: *Self) void { + for (1..vga_height) |y| { + for (0..vga_width) |x| { + const entry = self.getEntryAt(x, y); + self.putEntryAt(entry, x, y - 1); + } + } + self.clearRow(vga_height - 1); + self.column = 0; +} + +pub fn putCharAt(self: *Self, c: u8, new_color: u8, x: usize, y: usize) void { + self.putEntryAt(vgaEntry(c, new_color), x, y); +} + +fn putEntryAt(self: *Self, entry: u16, x: usize, y: usize) void { + const i = index(x, y); + self.buffer[i] = entry; +} + +fn getEntryAt(self: *Self, x: usize, y: usize) u16 { + const i = index(x, y); + return self.buffer[i]; +} + +pub fn putChar(self: *Self, c: u8) void { + switch (c) { + '\n' => self.newLine(), + else => { + if (self.column >= vga_width) { + self.newLine(); + } + const y = vga_height - 1; + const x = self.column; + self.putCharAt(c, self.color, x, y); + self.column += 1; + }, + } +} + +pub fn puts(self: *Self, data: []const u8) void { + for (data) |c| { + self.putChar(c); + } +} + +const Writer = std.io.Writer( + *Self, + error{}, + callback, +); + +pub fn writer(self: *Self) Writer { + return .{ .context = self }; +} + +fn callback(self: *Self, string: []const u8) error{}!usize { + self.puts(string); + return string.len; +} + +pub fn printf(self: *Self, comptime format: []const u8, args: anytype) void { + std.fmt.format(self.writer(), format, args) catch unreachable; +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..d9fb6bb --- /dev/null +++ b/src/main.zig @@ -0,0 +1,89 @@ +const lazy = @import("utils/Lazy.zig").lazy; +const Console = @import("Console.zig"); + +const ALIGN = 1 << 0; +const MEMINFO = 1 << 1; +const MAGIC = 0x1BADB002; +const FLAGS = ALIGN | MEMINFO; + +const MultibootHeader = packed struct { + magic: i32 = MAGIC, + flags: i32, + checksum: i32, + padding: u32 = 0, +}; + +export var mutliboot: MultibootHeader align(4) linksection(".multiboot") = .{ + .flags = FLAGS, + .checksum = -(MAGIC + FLAGS), +}; + +var stack_bytes: [16 * 1024]u8 align(16) linksection(".bss") = undefined; + +export fn _start() callconv(.Naked) noreturn { + asm volatile ( + \\ movl %[stack_top], %%esp + \\ movl %%esp, %%ebp + \\ call %[kmain:P] + : + : [stack_top] "i" (@as([*]align(16) u8, @ptrCast(&stack_bytes)) + @sizeOf(@TypeOf(stack_bytes))), + [kmain] "X" (&kmain), + ); +} + +var console = lazy(Console, .init); + +fn kmain() callconv(.C) void { + std.log.info("This is an info message", .{}); + std.log.debug("This is a debug message!", .{}); + std.log.warn("This is a warning message!", .{}); + std.log.err("This is an error message!", .{}); + + @panic("Uh oh"); +} + +const std = @import("std"); + +pub const panic = std.debug.FullPanic(kpanic); + +fn kpanic(msg: []const u8, first_trace_addr: ?usize) noreturn { + _ = first_trace_addr; + + std.log.err("PANIC: {s}", .{msg}); + + while (true) {} +} + +pub const std_options = std.Options{ + .log_level = .debug, + .logFn = klog, +}; + +fn klog( + comptime level: std.log.Level, + comptime scope: @Type(.enum_literal), + comptime format: []const u8, + args: anytype, +) void { + const c = console.get(); + + const color = switch (level) { + .err => Console.ConsoleColor.lightRed, + .warn => Console.ConsoleColor.lightBrown, + .info => Console.ConsoleColor.lightBlue, + .debug => Console.ConsoleColor.lightGreen, + }; + c.putChar('['); + c.setColor(color, Console.ConsoleColor.black); + c.puts(level.asText()); + c.resetColor(); + c.putChar(']'); + + c.putChar(' '); + + c.puts("(" ++ @tagName(scope) ++ ")"); + + c.putChar(' '); + + c.printf(format ++ "\n", args); +} diff --git a/src/utils/Lazy.zig b/src/utils/Lazy.zig new file mode 100644 index 0000000..674fd2c --- /dev/null +++ b/src/utils/Lazy.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn Lazy(comptime T: type, comptime init_fn: std.meta.DeclEnum(T)) type { + return struct { + const Self = @This(); + + value: ?T = null, + + pub fn get(self: *Self) *T { + return if (self.value) |*v| v else blk: { + self.value = @field(T, @tagName(init_fn))(); + break :blk &self.value.?; + }; + } + }; +} + +pub fn lazy(comptime T: type, comptime init_fn: std.meta.DeclEnum(T)) Lazy(T, init_fn) { + return .{}; +} From 5f2e19904d1f2d4cf41df41b1227888b5898480b Mon Sep 17 00:00:00 2001 From: ktkk Date: Fri, 19 Sep 2025 18:46:01 +0000 Subject: [PATCH 2/4] Initial commit --- .gitignore | 6 ++ build.zig | 44 +++++++++++++ flake.lock | 61 ++++++++++++++++++ flake.nix | 43 +++++++++++++ kernel.ld | 27 ++++++++ src/Console.zig | 153 +++++++++++++++++++++++++++++++++++++++++++++ src/main.zig | 93 +++++++++++++++++++++++++++ src/multiboot.zig | 56 +++++++++++++++++ src/utils/Lazy.zig | 20 ++++++ 9 files changed, 503 insertions(+) create mode 100644 .gitignore create mode 100644 build.zig create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 kernel.ld create mode 100644 src/Console.zig create mode 100644 src/main.zig create mode 100644 src/multiboot.zig create mode 100644 src/utils/Lazy.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30eaa82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.zig-cache/ +zig-out/ + +.envrc +.direnv/ + diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..3a4f63b --- /dev/null +++ b/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.resolveTargetQuery(.{ + .cpu_arch = std.Target.Cpu.Arch.x86, + .ofmt = std.Target.ObjectFormat.elf, + .abi = std.Target.Abi.none, + .os_tag = std.Target.Os.Tag.freestanding, + .cpu_features_sub = std.Target.x86.featureSet(&.{ + .mmx, + .sse, + .sse2, + .avx, + .avx2, + }), + .cpu_features_add = std.Target.x86.featureSet(&.{ + .soft_float, + }), + }); + + const optimize = b.standardOptimizeOption(.{}); + + const kernel = b.addExecutable(.{ + .name = "kernel", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("src/main.zig"), + }), + }); + kernel.setLinkerScript(b.path("kernel.ld")); + + b.installArtifact(kernel); + + const qemu = b.addSystemCommand(&[_][]const u8{ + "qemu-system-x86_64", + }); + qemu.addArg("-kernel"); + qemu.addFileArg(kernel.getEmittedBin()); + + const run = b.step("run", "Run QEMU"); + run.dependOn(&qemu.step); +} + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..d680c26 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1757745802, + "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..902e1e9 --- /dev/null +++ b/flake.nix @@ -0,0 +1,43 @@ +{ + description = "Zig Template"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + nixpkgs, + flake-utils, + ... + }: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = nixpkgs.legacyPackages.${system}; + nativeBuildInputs = with pkgs; [ + zig + zls + ]; + + buildInputs = with pkgs; [ + qemu + grub2 + ]; + in { + devShells.default = pkgs.mkShell {inherit nativeBuildInputs buildInputs;}; + + packages.default = pkgs.stdenv.mkDerivation { + pname = "template"; + version = "0.0.0"; + src = ./.; + + nativeBuildInputs = + nativeBuildInputs + ++ [ + pkgs.zig.hook + ]; + inherit buildInputs; + }; + } + ); +} diff --git a/kernel.ld b/kernel.ld new file mode 100644 index 0000000..447c7ed --- /dev/null +++ b/kernel.ld @@ -0,0 +1,27 @@ +ENTRY(_start) + +SECTIONS { + . = 1M; + + .multiboot : ALIGN(4K) { + KEEP(*(.multiboot)) + } + + .text : ALIGN(4K) { + *(.text) + } + + .rodata : ALIGN(4K) { + *(.rodata) + } + + .data : ALIGN(4K) { + *(.data); + } + + .bss : ALIGN(4K) { + *(COMMON) + *(.bss) + } +} + diff --git a/src/Console.zig b/src/Console.zig new file mode 100644 index 0000000..0825e73 --- /dev/null +++ b/src/Console.zig @@ -0,0 +1,153 @@ +const std = @import("std"); + +const Self = @This(); + +pub fn init() Self { + var self = Self{}; + self.clear(); + return self; +} + +const vga_width = 80; +const vga_height = 25; +const vga_size = vga_width * vga_height; + +pub const ConsoleColor = enum(u8) { + black = 0, + blue = 1, + green = 2, + cyan = 3, + red = 4, + magenta = 5, + brown = 6, + light_gray = 7, + dark_gray = 8, + light_blue = 9, + light_green = 10, + light_cyan = 11, + light_red = 12, + light_magenta = 13, + light_brown = 14, + white = 15, +}; + +const default_color = vgaEntryColor(ConsoleColor.light_gray, ConsoleColor.black); + +column: usize = 0, +color: u8 = default_color, +buffer: [*]volatile u16 = @ptrFromInt(0xb8000), + +fn vgaEntryColor(fg: ConsoleColor, bg: ConsoleColor) u8 { + return @intFromEnum(fg) | (@intFromEnum(bg) << 4); +} + +fn vgaEntry(uc: u8, new_color: u8) u16 { + const c: u16 = new_color; + return uc | (c << 8); +} + +fn index(x: usize, y: usize) usize { + return y * vga_width + x; +} + +pub fn setColor(self: *Self, fg: ConsoleColor, bg: ConsoleColor) void { + self.color = vgaEntryColor(fg, bg); +} + +pub fn resetColor(self: *Self) void { + self.color = default_color; +} + +pub fn clear(self: *Self) void { + @memset(self.buffer[0..vga_size], vgaEntry(' ', self.color)); +} + +fn clearRow(self: *Self, y: usize) void { + for (0..vga_width) |x| { + const i = index(x, y); + self.buffer[i] = vgaEntry(' ', self.color); + } +} + +fn newLine(self: *Self) void { + for (1..vga_height) |y| { + for (0..vga_width) |x| { + const entry = self.getEntryAt(x, y); + self.putEntryAt(entry, x, y - 1); + } + } + self.clearRow(vga_height - 1); + self.column = 0; +} + +pub fn putCharAt(self: *Self, c: u8, new_color: u8, x: usize, y: usize) void { + self.putEntryAt(vgaEntry(c, new_color), x, y); +} + +fn putEntryAt(self: *Self, entry: u16, x: usize, y: usize) void { + const i = index(x, y); + self.buffer[i] = entry; +} + +fn getEntryAt(self: *Self, x: usize, y: usize) u16 { + const i = index(x, y); + return self.buffer[i]; +} + +pub fn putChar(self: *Self, c: u8) void { + switch (c) { + '\n' => self.newLine(), + else => { + if (self.column >= vga_width) { + self.newLine(); + } + const y = vga_height - 1; + const x = self.column; + self.putCharAt(c, self.color, x, y); + self.column += 1; + }, + } +} + +pub fn puts(self: *Self, data: []const u8) void { + for (data) |c| { + self.putChar(c); + } +} + +const Writer = struct { + console: *Self, + interface: std.Io.Writer, + + fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + _ = splat; + + const self: *@This() = @alignCast(@fieldParentPtr("interface", w)); + + const buffered = w.buffered(); + if (buffered.len != 0) { + self.console.puts(buffered); + return w.consume(buffered.len); + } + self.console.puts(data[0]); + return data[0].len; + } +}; + +fn writer(self: *Self, buffer: []u8) Writer { + return .{ + .console = self, + .interface = .{ + .buffer = buffer, + .vtable = &.{ + .drain = Writer.drain, + }, + }, + }; +} + +pub fn printf(self: *Self, comptime format: []const u8, args: anytype) void { + var w = self.writer(&.{}); + w.interface.print(format, args) catch unreachable; +} + diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..727bbc9 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,93 @@ +const lazy = @import("utils/Lazy.zig").lazy; +const Console = @import("Console.zig"); + +const ALIGN = 1 << 0; +const MEMINFO = 1 << 1; +const MAGIC = 0x1BADB002; +const FLAGS = ALIGN | MEMINFO; + +const MultibootHeader = packed struct { + magic: i32 = MAGIC, + flags: i32, + checksum: i32, + padding: u32 = 0, +}; + +export var mutliboot: MultibootHeader align(4) linksection(".multiboot") = .{ + .flags = FLAGS, + .checksum = -(MAGIC + FLAGS), +}; + +var stack_bytes: [16 * 1024]u8 align(16) linksection(".bss") = undefined; + +export fn _start() callconv(.naked) noreturn { + asm volatile ( + \\ movl %[stack_top], %%esp + \\ movl %%esp, %%ebp + \\ call %[kmain:P] + : + : [stack_top] "i" (@as([*]align(16) u8, @ptrCast(&stack_bytes)) + @sizeOf(@TypeOf(stack_bytes))), + [kmain] "X" (&kmain), + ); +} + +var console = lazy(Console, .init); + +fn kmain() callconv(.c) void { + std.log.info("This is an info message", .{}); + std.log.debug("This is a debug message!", .{}); + std.log.warn("This is a warning message!", .{}); + std.log.err("This is an error message!", .{}); + + @panic("Uh oh"); +} + +const std = @import("std"); + +pub const panic = std.debug.FullPanic(kpanic); + +fn kpanic(msg: []const u8, first_trace_addr: ?usize) noreturn { + _ = first_trace_addr; + + std.log.err("PANIC: {s}", .{msg}); + + while (true) {} +} + +pub const std_options = std.Options{ + .log_level = .debug, + .logFn = klog, +}; + +fn klog( + comptime level: std.log.Level, + comptime scope: @Type(.enum_literal), + comptime format: []const u8, + args: anytype, +) void { + const c = console.get(); + + const color = switch (level) { + .err => Console.ConsoleColor.light_red, + .warn => Console.ConsoleColor.light_brown, + .info => Console.ConsoleColor.light_blue, + .debug => Console.ConsoleColor.light_green, + }; + c.putChar('['); + { + c.setColor(color, Console.ConsoleColor.black); + defer c.resetColor(); + + c.puts(level.asText()); + } + c.putChar(']'); + + c.putChar(' '); + + c.puts("(" ++ @tagName(scope) ++ ")"); + + c.putChar(' '); + + c.printf(format ++ "\n", args); +} + diff --git a/src/multiboot.zig b/src/multiboot.zig new file mode 100644 index 0000000..d30a255 --- /dev/null +++ b/src/multiboot.zig @@ -0,0 +1,56 @@ +pub const Info = extern struct { + flags: c_uint, + mem_lower: c_uint, + mem_upper: c_uint, + boot_device: c_uint, + cmdline: c_uint, + mods_count: c_uint, + mods_addr: c_uint, + u: extern union { + aout_sym: extern struct { + tabsize: c_uint, + strsize: c_uint, + addr: c_uint, + reserved: c_uint, + }, + elf_sec: extern struct { + num: c_uint, + size: c_uint, + addr: c_uint, + shndx: c_uint, + }, + }, + mmap_length: c_uint, + mmap_addr: c_uint, + drives_length: c_uint, + drives_addr: c_uint, + config_table: c_uint, + boot_loader_name: c_uint, + apm_table: c_uint, + vbe_control_info: c_uint, + vbe_move_info: c_uint, + vbe_mode: c_ushort, + vbe_interface_seg: c_ushort, + vbe_interface_off: c_ushort, + vbe_interface_len: c_ushort, + framebuffer_addr: c_ulonglong, + framebuffer_pitch: c_uint, + framebuffer_width: c_uint, + framebuffer_height: c_uint, + framebuffer_bpp: u8, + framebuffer_type: u8, + unnamed_0: extern union { + unnamed_0: extern struct { + framebuffer_palette_addr: c_uint, + framebuffer_palette_num_colors: c_ushort, + }, + unnamed_1: extern struct { + framebuffer_red_field_position: u8, + framebuffer_red_mask_size: u8, + framebuffer_green_field_position: u8, + framebuffer_green_mask_size: u8, + framebuffer_blue_field_position: u8, + framebuffer_blue_mask_size: u8, + }, + }, +}; diff --git a/src/utils/Lazy.zig b/src/utils/Lazy.zig new file mode 100644 index 0000000..674fd2c --- /dev/null +++ b/src/utils/Lazy.zig @@ -0,0 +1,20 @@ +const std = @import("std"); + +pub fn Lazy(comptime T: type, comptime init_fn: std.meta.DeclEnum(T)) type { + return struct { + const Self = @This(); + + value: ?T = null, + + pub fn get(self: *Self) *T { + return if (self.value) |*v| v else blk: { + self.value = @field(T, @tagName(init_fn))(); + break :blk &self.value.?; + }; + } + }; +} + +pub fn lazy(comptime T: type, comptime init_fn: std.meta.DeclEnum(T)) Lazy(T, init_fn) { + return .{}; +} From a5c560d2b97adc1ebd70897157514f302d42f34b Mon Sep 17 00:00:00 2001 From: ktkk Date: Fri, 7 Nov 2025 10:29:21 +0000 Subject: [PATCH 3/4] Update to Zig 0.15.2 --- build.zig | 10 ++++--- flake.lock | 6 ++--- src/Console.zig | 70 ++++++++++++++++++++++++++++++++++--------------- src/main.zig | 11 ++++---- 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/build.zig b/build.zig index 05ff4db..e256efa 100644 --- a/build.zig +++ b/build.zig @@ -22,10 +22,12 @@ pub fn build(b: *std.Build) !void { const kernel = b.addExecutable(.{ .name = "kernel.elf", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = .ReleaseFast, - .code_model = .kernel, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .ReleaseFast, + .code_model = .kernel, + }), }); kernel.setLinkerScript(b.path("kernel.ld")); diff --git a/flake.lock b/flake.lock index 2272d3d..bbb64ca 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1742669843, - "narHash": "sha256-G5n+FOXLXcRx+3hCJ6Rt6ZQyF1zqQ0DL0sWAMn2Nk0w=", + "lastModified": 1762363567, + "narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1e5b653dff12029333a6546c11e108ede13052eb", + "rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4", "type": "github" }, "original": { diff --git a/src/Console.zig b/src/Console.zig index e7bda52..6b75378 100644 --- a/src/Console.zig +++ b/src/Console.zig @@ -20,18 +20,18 @@ pub const ConsoleColor = enum(u8) { red = 4, magenta = 5, brown = 6, - lightGray = 7, - darkGray = 8, - lightBlue = 9, - lightGreen = 10, - lightCyan = 11, - lightRed = 12, - lightMagenta = 13, - lightBrown = 14, + light_gray = 7, + dark_gray = 8, + light_blue = 9, + light_green = 10, + light_cyan = 11, + light_red = 12, + light_magenta = 13, + light_brown = 14, white = 15, }; -const default_color = vgaEntryColor(ConsoleColor.lightGray, ConsoleColor.black); +const default_color = vgaEntryColor(ConsoleColor.light_gray, ConsoleColor.black); column: usize = 0, color: u8 = default_color, @@ -115,21 +115,49 @@ pub fn puts(self: *Self, data: []const u8) void { } } -const Writer = std.io.Writer( - *Self, - error{}, - callback, -); +const Writer = struct { + console: *Self, + interface: std.Io.Writer, -pub fn writer(self: *Self) Writer { - return .{ .context = self }; -} + fn drain(io_w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + const self: *@This() = @fieldParentPtr("interface", io_w); -fn callback(self: *Self, string: []const u8) error{}!usize { - self.puts(string); - return string.len; + try self.puts(io_w.buffered()); + io_w.end = 0; + + for (data[0..data.len - 1]) |bytes| { + try self.puts(bytes); + } + const pattern = data[data.len - 1]; + for (0..splat) |_| { + try self.puts(pattern); + } + + return std.Io.Writer.countSplat(data, splat); + } + + fn puts(self: *@This(), data: []const u8) std.Io.Writer.Error!void { + self.console.puts(data); + } +}; + +pub fn writer(self: *Self, buffer: []u8) Writer { + return .{ + .console = self, + .interface = .{ + .buffer = buffer, + .vtable = &.{ + .drain = Writer.drain, + }, + }, + }; } pub fn printf(self: *Self, comptime format: []const u8, args: anytype) void { - std.fmt.format(self.writer(), format, args) catch unreachable; + var buffer: [1024]u8 = undefined; + var w = self.writer(&buffer); + const io_w = &w.interface; + io_w.print(format, args) catch unreachable; + io_w.flush() catch unreachable; } + diff --git a/src/main.zig b/src/main.zig index d9fb6bb..2e2a80d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -20,7 +20,7 @@ export var mutliboot: MultibootHeader align(4) linksection(".multiboot") = .{ var stack_bytes: [16 * 1024]u8 align(16) linksection(".bss") = undefined; -export fn _start() callconv(.Naked) noreturn { +export fn _start() callconv(.naked) noreturn { asm volatile ( \\ movl %[stack_top], %%esp \\ movl %%esp, %%ebp @@ -68,11 +68,12 @@ fn klog( const c = console.get(); const color = switch (level) { - .err => Console.ConsoleColor.lightRed, - .warn => Console.ConsoleColor.lightBrown, - .info => Console.ConsoleColor.lightBlue, - .debug => Console.ConsoleColor.lightGreen, + .err => Console.ConsoleColor.light_red, + .warn => Console.ConsoleColor.light_brown, + .info => Console.ConsoleColor.light_blue, + .debug => Console.ConsoleColor.light_green, }; + c.putChar('['); c.setColor(color, Console.ConsoleColor.black); c.puts(level.asText()); From 2e9292be6e7903c924449d5b7e2e0d4b46edc6c7 Mon Sep 17 00:00:00 2001 From: ktkk Date: Fri, 7 Nov 2025 10:41:41 +0000 Subject: [PATCH 4/4] Refactor and use the Multiboot info struct --- src/main.zig | 44 ++++++++++++++-------- src/multiboot.zig | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 src/multiboot.zig diff --git a/src/main.zig b/src/main.zig index 2e2a80d..dd4f020 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,21 +1,23 @@ const lazy = @import("utils/Lazy.zig").lazy; const Console = @import("Console.zig"); +const MultibootInfo = @import("multiboot.zig").Info; + +const MultibootHeader = packed struct { + magic: i32, + flags: i32, + checksum: i32, + padding: u32, +}; const ALIGN = 1 << 0; const MEMINFO = 1 << 1; const MAGIC = 0x1BADB002; const FLAGS = ALIGN | MEMINFO; - -const MultibootHeader = packed struct { - magic: i32 = MAGIC, - flags: i32, - checksum: i32, - padding: u32 = 0, -}; - export var mutliboot: MultibootHeader align(4) linksection(".multiboot") = .{ + .magic = MAGIC, .flags = FLAGS, .checksum = -(MAGIC + FLAGS), + .padding = 0, }; var stack_bytes: [16 * 1024]u8 align(16) linksection(".bss") = undefined; @@ -24,6 +26,8 @@ export fn _start() callconv(.naked) noreturn { asm volatile ( \\ movl %[stack_top], %%esp \\ movl %%esp, %%ebp + \\ push %%ebx + \\ push %%eax \\ call %[kmain:P] : : [stack_top] "i" (@as([*]align(16) u8, @ptrCast(&stack_bytes)) + @sizeOf(@TypeOf(stack_bytes))), @@ -33,13 +37,23 @@ export fn _start() callconv(.naked) noreturn { var console = lazy(Console, .init); -fn kmain() callconv(.C) void { - std.log.info("This is an info message", .{}); - std.log.debug("This is a debug message!", .{}); - std.log.warn("This is a warning message!", .{}); - std.log.err("This is an error message!", .{}); +fn kmain(magic: u32, info: *const MultibootInfo) callconv(.c) void { + std.debug.assert(magic == MAGIC); - @panic("Uh oh"); + std.log.info("{any}", .{info}); + + if (info.isValid(.boot_loader_name)) { + std.log.info("Booted by {s}", .{info.boot_loader_name}); + } + if (info.isValid(.cmdline)) { + std.log.info("Booted with cmdline \"{s}\"", .{info.cmdline}); + } + + hang(); +} + +fn hang() noreturn { + while (true) asm volatile ("hlt"); } const std = @import("std"); @@ -51,7 +65,7 @@ fn kpanic(msg: []const u8, first_trace_addr: ?usize) noreturn { std.log.err("PANIC: {s}", .{msg}); - while (true) {} + hang(); } pub const std_options = std.Options{ diff --git a/src/multiboot.zig b/src/multiboot.zig new file mode 100644 index 0000000..9863d22 --- /dev/null +++ b/src/multiboot.zig @@ -0,0 +1,96 @@ +const std = @import("std"); + +const BootModule = packed struct { + start: u32, + end: u32, + string: ?[*:0]u8, + reserved: u32, +}; + +pub const Info = packed struct { + flags: std.bit_set.IntegerBitSet(32), + mem_lower: u32, + mem_upper: u32, + boot_device: packed struct { + part3: u8, + part2: u8, + part1: u8, + drive: u8, + }, + cmdline: [*:0]u8, + mods_count: u32, + mods_addr: [*]BootModule, + u: packed union { + aout_sym: packed struct { + tabsize: u32, + strsize: u32, + addr: u32, + reserved: u32, + }, + elf_sec: packed struct { + num: u32, + size: u32, + addr: u32, + shndx: u32, + }, + }, + mmap_length: u32, + mmap_addr: u32, + drives_length: u32, + drives_addr: u32, + config_table: u32, + boot_loader_name: [*:0]u8, + apm_table: u32, + vbe: packed struct { + control_info: u32, + move_info: u32, + mode: u16, + interface_seg: u16, + interface_off: u16, + interface_len: u16, + }, + framebuffer: packed struct { + addr: u64, + pitch: u32, + width: u32, + height: u32, + bpp: u8, + type: u8, + color_info: packed union { + indexed_color: packed struct { + framebuffer_palette_addr: u32, + framebuffer_palette_num_colors: u16, + }, + rgb_color: packed struct { + framebuffer_red_field_position: u8, + framebuffer_red_mask_size: u8, + framebuffer_green_field_position: u8, + framebuffer_green_mask_size: u8, + framebuffer_blue_field_position: u8, + framebuffer_blue_mask_size: u8, + }, + }, + }, + + const Self = @This(); + + pub fn isValid(self: *const Self, comptime field: std.meta.FieldEnum(Self)) bool { + return switch (field) { + .flags => true, + .mem_lower, .mem_upper => self.flags.isSet(0), + .boot_device => self.flags.isSet(1), + .cmdline => self.flags.isSet(2), + .mods_count, .mods_addr => self.flags.isSet(3), + // TODO: .u => either 4 or 5 + .mmap_length, .mmap_addr => self.flags.isSet(6), + .drives_length, .drives_addr => self.flags.isSet(7), + .config_table => self.flags.isSet(8), + .boot_loader_name => self.flags.isSet(9), + .apm_table => self.flags.isSet(10), + .vbe => self.flags.isSet(11), + .framebuffer => self.flags.isSet(12), + else => false, + }; + } +}; +