summaryrefslogtreecommitdiff
path: root/x86-bare-metal/multiboot/vga.zig
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2025-04-24 00:21:02 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2025-04-24 00:21:02 +0200
commit23e0ccf7b5a0ccea545f231d35dbecc00011a9de (patch)
treefcabb2e084297bb51c2a12ee77404b891cca0df6 /x86-bare-metal/multiboot/vga.zig
parent20a854354918735c3289c5576a28fad18ca21757 (diff)
downloadzig-playground-main.tar.gz
zig-playground-main.zip
multiboot: add multiboot example kernelHEADmain
Diffstat (limited to 'x86-bare-metal/multiboot/vga.zig')
-rw-r--r--x86-bare-metal/multiboot/vga.zig117
1 files changed, 117 insertions, 0 deletions
diff --git a/x86-bare-metal/multiboot/vga.zig b/x86-bare-metal/multiboot/vga.zig
new file mode 100644
index 0000000..10d222c
--- /dev/null
+++ b/x86-bare-metal/multiboot/vga.zig
@@ -0,0 +1,117 @@
+/// VGA text mode console [1]
+///
+/// 8 colors + 8 bright variants:
+/// - 0x0 black (0x8 dark gray)
+/// - 0x1 blue (0x9 light blue)
+/// - 0x2 green (0xa light green)
+/// - 0x3 cyan (0xb light cyan)
+/// - 0x4 red (0xc light red)
+/// - 0x5 magenta (0xd pink)
+/// - 0x6 brown (0xe yellow)
+/// - 0x7 light gray (0xf white)
+///
+/// [1] https://en.wikipedia.org/wiki/VGA_text_mode
+pub fn vga(fg: u4, bg: u3) type {
+ return struct {
+ const Self = @This();
+
+ // Text mode dimensions (mode 3h text mode 80x25).
+ const COLS = 80;
+ const ROWS = 25;
+ // Slice into VGA video memory.
+ const VIDEO: []u16 = @as([*]u16, @ptrFromInt(0xB8000))[0 .. COLS * ROWS];
+
+ /// Current index into last line.
+ col: usize,
+
+ /// Default value.
+ pub const init: Self = .{
+ .col = 0,
+ };
+
+ /// Write string to screen and move cursor.
+ pub fn puts(self: *Self, msg: []const u8) void {
+ for (msg) |char| {
+ self.putcImpl(char);
+ }
+ self.updateCursor();
+ }
+
+ /// Write character to screen and move cursor.
+ pub fn putc(self: *Self, char: u8) void {
+ self.putcImpl(char);
+ self.updateCursor();
+ }
+
+ /// Write character to screen.
+ fn putcImpl(self: *Self, char: u8) void {
+ if (char == '\n') {
+ self.lf();
+ return;
+ }
+
+ const idx = (ROWS - 1) * COLS + self.col;
+ VIDEO[idx] = vgaEntry(char, fg, bg);
+
+ self.col += 1;
+ if (self.col == COLS) {
+ self.lf();
+ }
+ }
+
+ /// Handle linefeed (newline).
+ /// Move up all lines by one, shifting out the upper most line.
+ fn lf(self: *Self) void {
+ // Shift all rows one up, copy rows [1. ROWS[-> [0, ROWS-1[.
+ for (0..(ROWS - 1) * COLS) |idx| {
+ VIDEO[idx] = VIDEO[1 * COLS + idx];
+ }
+ // Clear ROWS-1 (last row).
+ for (0..COLS) |idx| {
+ VIDEO[(ROWS - 1) * COLS + idx] = vgaEntry(' ', fg, bg);
+ }
+ self.col = 0;
+ }
+
+ /// Update cursor to current position.
+ /// https://wiki.osdev.org/Text_Mode_Cursor#Without_the_BIOS
+ fn updateCursor(self: Self) void {
+ const idx = (ROWS - 1) * COLS + self.col;
+
+ outb(0x3d4, 0xf);
+ outb(0x3d5, @truncate(idx & 0xff));
+ outb(0x3d4, 0xe);
+ outb(0x3d5, @truncate(idx >> 8 & 0xff));
+ }
+
+ const Writer = @import("std").io.GenericWriter(*Self, anyerror, struct {
+ fn write(ctx: *Self, bytes: []const u8) anyerror!usize {
+ ctx.puts(bytes);
+ return bytes.len;
+ }
+ }.write);
+
+ pub fn writer(self: *Self) Writer {
+ return .{ .context = self };
+ }
+ };
+}
+
+// Each VGA (text mode) video buffer entry is 16bit wide.
+// [15] blink
+// [14:12] bg color (3 bit)
+// [11: 8] fg color (4 bit)
+// [ 7: 0] ascii character
+//
+// https://en.wikipedia.org/wiki/VGA_text_mode
+inline fn vgaEntry(char: u8, fg: u4, bg: u3) u16 {
+ return @as(u16, bg) << 12 | @as(u16, fg) << 8 | char;
+}
+
+inline fn outb(port: u16, val: u8) void {
+ _ = asm volatile ("outb %[al], %[dx]"
+ : // no outputs
+ : [al] "{al}" (val),
+ [dx] "{dx}" (port),
+ );
+}