hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-10 07:17:15 +0200
committerhachem <im@hachem.wtf>2026-09-10 07:17:15 +0200
commitdbe50063b0f78ecbcb14ade65e16c5c74ad9abe1 (patch)
tree2a4c08c6772b710cb30a0a586124196b5dabe954 /boot
parent0434097a393e790085911bad5229a6654876dae3 (diff)
feat: basic framebuffer
Diffstat (limited to 'boot')
-rw-r--r--boot/boot.zig23
-rw-r--r--boot/framebuffer.zig26
-rw-r--r--boot/limine.zig41
-rw-r--r--boot/serial.zig6
4 files changed, 90 insertions, 6 deletions
diff --git a/boot/boot.zig b/boot/boot.zig
index a7822af..2303409 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const limine = @import("limine.zig");
const serial = @import("serial.zig");
+const framebuffer = @import("framebuffer.zig");
// We need a panic handler otherwise zig won't be happy,
// std usually formats a message through std.Io.Writer which
@@ -9,9 +10,9 @@ const serial = @import("serial.zig");
pub const panic = std.debug.FullPanic(struct {
fn halt(msg: []const u8, _: ?usize) noreturn {
serial.init();
- serial.write("\n!!! uhhhh engine kaput : ");
+ serial.write("\r\n!!! uhhhh engine kaput : ");
serial.write(msg);
- serial.write("\n");
+ serial.write("\r\n");
while (true) asm volatile ("hlt");
}
}.halt);
@@ -24,6 +25,22 @@ export var requests_end: limine.RequestsEndMarker linksection(".limine_requests_
// ENTRY(_start)
export fn _start() callconv(.c) noreturn {
serial.init();
- serial.write("if you see this, it means revel didn't shit the bed");
+ serial.write("if you see this, it means revel didn't shit the bed\r\n");
+
+ if (framebuffer.get()) |screen| {
+ // XOR texture
+ var y: usize = 0;
+ while (y < screen.height) : (y += 1) {
+ var x: usize = 0;
+ while (x < screen.width) : (x += 1) {
+ const v: u32 = @intCast((x ^ y) & 0xFF);
+ framebuffer.putpixel(screen, x, y, (v << 16) | (v << 8) | v);
+ }
+ }
+ serial.write("drew to the framebuffer\r\n");
+ } else {
+ serial.write("limine was a piece of shit.\r\n");
+ }
+
while (true) asm volatile ("hlt");
}
diff --git a/boot/framebuffer.zig b/boot/framebuffer.zig
new file mode 100644
index 0000000..6518b17
--- /dev/null
+++ b/boot/framebuffer.zig
@@ -0,0 +1,26 @@
+// linear framebuffer type shi you feel me?
+// see: https://wiki.osdev.org/Limine_Bare_Bones
+
+const limine = @import("limine.zig");
+
+pub const Framebuffer = limine.Framebuffer;
+
+// the request placed in .limine_requests so the loader finds and fills it
+// idk why this sounds vaguely erotic, like yeah fill me harder lol
+export var request: limine.FramebufferRequest linksection(".limine_requests") = .{};
+
+// the response pointer is written by Limine before we run, so read it through a
+// volatile pointer or the optimizer assumes it's still null
+pub fn get() ?*Framebuffer {
+ const req: *volatile limine.FramebufferRequest = &request;
+ const resp = req.response orelse return null;
+ if (resp.framebuffer_count == 0) return null;
+ return resp.framebuffers.?[0];
+}
+
+// assumes the usual Limine RGB 32-bpp layout im sure i wont forget about this layout
+// later and i wont fuck me over
+pub fn putpixel(fb: *Framebuffer, x: usize, y: usize, color: u32) void {
+ const pixel: *align(1) u32 = @ptrCast(fb.address + y * fb.pitch + x * 4);
+ pixel.* = color;
+}
diff --git a/boot/limine.zig b/boot/limine.zig
index e9aac9c..ef08867 100644
--- a/boot/limine.zig
+++ b/boot/limine.zig
@@ -25,3 +25,44 @@ pub const RequestsEndMarker = extern struct {
m0: u64 = 0xadc0e0531bb10d03,
m1: u64 = 0x9572709f31764c62,
};
+
+// Every request id starts with this common magic, then two request-specific
+// words. Limine scans .limine_requests for these and fills in `response`.
+fn requestId(a: u64, b: u64) [4]u64 {
+ return .{ 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, a, b };
+}
+
+// --- Framebuffer feature: ask Limine for a linear framebuffer ---
+
+pub const Framebuffer = extern struct {
+ address: [*]u8,
+ width: u64,
+ height: u64,
+ pitch: u64, // bytes per row (not always width*bpp/8)
+ bpp: u16, // bits per pixel
+ memory_model: u8, // 1 = RGB
+ red_mask_size: u8,
+ red_mask_shift: u8,
+ green_mask_size: u8,
+ green_mask_shift: u8,
+ blue_mask_size: u8,
+ blue_mask_shift: u8,
+ // 7 bytes of padding land here (extern-struct alignment before edid_size)
+ edid_size: u64,
+ edid: ?*anyopaque,
+ // response revision 1 and up — unused for now:
+ mode_count: u64,
+ modes: ?*anyopaque,
+};
+
+pub const FramebufferResponse = extern struct {
+ revision: u64,
+ framebuffer_count: u64,
+ framebuffers: ?[*]*Framebuffer,
+};
+
+pub const FramebufferRequest = extern struct {
+ id: [4]u64 = requestId(0x9d5827dcd881dd75, 0xa3148604f6fab11b),
+ revision: u64 = 0,
+ response: ?*FramebufferResponse = null,
+};
diff --git a/boot/serial.zig b/boot/serial.zig
index cd6bd13..4863864 100644
--- a/boot/serial.zig
+++ b/boot/serial.zig
@@ -1,6 +1,6 @@
-// This is a minimal 16550 UART driver for COM1, this is
-// mainly going to be used for debugging since its really
-// simple to setup. Fun fact: i was drunk when i wrote this
+// This is a minimal 16550 UART driver for COM1, this is mainly going
+// to be used for debugging since its really simple to setup.
+// Fun fact: i was drunk when i wrote this
//
// see: https://wiki.osdev.org/Serial_Ports