//! A framebuffer text console: draws glyphs from an embedded PSF2 font directly //! into the linear framebuffer the bootloader handed us. No firmware, no driver //! — just pixels. This is the kernel's first output device. const std = @import("std"); const danos = @import("danos"); /// The console font, embedded at compile time. cp850-8x16, PSF2 format: /// a 32-byte header, then 256 glyphs of 16 bytes each (one byte per 8-pixel /// row). We index glyphs straight by byte value, so ASCII maps 1:1. const font = @embedFile("font.psf"); const glyph_w = 8; const glyph_h = 16; const glyph_bytes = glyph_h; // 8 pixels wide => 1 byte per row const glyph_data = 32; // PSF2 header size pub const Console = struct { fb: danos.Framebuffer, cols: u32, rows: u32, col: u32 = 0, row: u32 = 0, fg: u32 = 0x00c8_c8c8, // light grey bg: u32 = 0x0000_0000, // black pub fn init(fb: danos.Framebuffer) Console { return .{ .fb = fb, .cols = fb.width / glyph_w, .rows = fb.height / glyph_h, }; } /// Fill the whole screen with the background colour and home the cursor. pub fn clear(self: *Console) void { var y: u32 = 0; while (y < self.fb.height) : (y += 1) self.fillRow(y, self.bg); self.col = 0; self.row = 0; } pub fn write(self: *Console, bytes: []const u8) void { for (bytes) |c| self.putChar(c); } /// Formatted output, e.g. `con.print("x={d}\n", .{x})`. Silently truncates /// past 256 bytes — this is a debug console, not a general writer. pub fn print(self: *Console, comptime fmt: []const u8, args: anytype) void { var buf: [256]u8 = undefined; self.write(std.fmt.bufPrint(&buf, fmt, args) catch return); } pub fn putChar(self: *Console, ch: u8) void { switch (ch) { '\n' => self.newline(), '\r' => self.col = 0, else => { if (self.col >= self.cols) self.newline(); self.drawGlyph(ch, self.col * glyph_w, self.row * glyph_h); self.col += 1; }, } } fn newline(self: *Console) void { self.col = 0; if (self.row + 1 >= self.rows) { self.scroll(); } else { self.row += 1; } } fn drawGlyph(self: *Console, ch: u8, px: u32, py: u32) void { const rows = font[glyph_data + @as(usize, ch) * glyph_bytes ..][0..glyph_bytes]; var gy: u32 = 0; while (gy < glyph_h) : (gy += 1) { const bits = rows[gy]; var gx: u32 = 0; while (gx < glyph_w) : (gx += 1) { // Leftmost pixel is the high bit. const on = (bits >> @as(u3, @intCast(7 - gx))) & 1 != 0; self.pixel(px + gx, py + gy, if (on) self.fg else self.bg); } } } /// Shift the visible text up one glyph row and clear the freed bottom row, /// leaving the cursor on that now-blank last line. fn scroll(self: *Console) void { const visible = self.rows * glyph_h; var y: u32 = 0; while (y + glyph_h < visible) : (y += 1) self.copyRow(y, y + glyph_h); while (y < visible) : (y += 1) self.fillRow(y, self.bg); self.row = self.rows - 1; } inline fn rowPtr(self: *Console, y: u32) [*]volatile u32 { const base: [*]volatile u8 = @ptrFromInt(self.fb.base); return @ptrCast(@alignCast(base + y * self.fb.pitch)); } inline fn pixel(self: *Console, x: u32, y: u32, color: u32) void { self.rowPtr(y)[x] = color; } fn fillRow(self: *Console, y: u32, color: u32) void { const row = self.rowPtr(y); var x: u32 = 0; while (x < self.fb.width) : (x += 1) row[x] = color; } fn copyRow(self: *Console, dst_y: u32, src_y: u32) void { const dst = self.rowPtr(dst_y); const src = self.rowPtr(src_y); var x: u32 = 0; while (x < self.fb.width) : (x += 1) dst[x] = src[x]; } };