summaryrefslogtreecommitdiff
path: root/x86-bare-metal/multiboot/vga.zig
blob: 10d222c0e534bd351132f2dc35816b563a6efb09 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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),
    );
}