summaryrefslogtreecommitdiff
path: root/kernel/lib/framebuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/lib/framebuffer.cpp')
-rw-r--r--kernel/lib/framebuffer.cpp374
1 files changed, 374 insertions, 0 deletions
diff --git a/kernel/lib/framebuffer.cpp b/kernel/lib/framebuffer.cpp
new file mode 100644
index 0000000..476abaa
--- /dev/null
+++ b/kernel/lib/framebuffer.cpp
@@ -0,0 +1,374 @@
+// ============================================================================
+// lib/framebuffer.cpp - Early kernel framebuffer console
+//
+// Renders text directly to the linear framebuffer using a minimal
+// built-in 8x16 bitmap font (PC VGA style, ASCII 32-126).
+// ============================================================================
+
+#include <kernel/kprint.h>
+#include <stdarg.h>
+
+namespace kcon {
+
+// ── Character cell dimensions ──────────────────────────────────────────────
+
+static constexpr uint32_t CHAR_W = 8; // Font glyph width in pixels
+static constexpr uint32_t CHAR_H = 16; // Font glyph height in pixels
+static constexpr uint32_t TAB_WIDTH = 4; // Tab stop width in character cells
+
+// ── Colors (0x00RRGGBB) ────────────────────────────────────────────────────
+
+static constexpr uint32_t FG_COLOR = CONFIG_FBCON_FG_COLOR; // Light grey
+static constexpr uint32_t BG_COLOR = CONFIG_FBCON_BG_COLOR; // Dark blue
+
+// ── Console state ──────────────────────────────────────────────────────────
+//
+// All mutable state lives in one struct so callers can reason about it as
+// a unit and we avoid scattered bare globals.
+#pragma message("when i add drivers abstract, move it to another console interface and use framebuffer as driver")
+
+struct Console {
+public:
+ uint32_t* fb = nullptr; // Framebuffer base (as 32-bit pixels)
+ uint32_t width = 0; // Pixels per row
+ uint32_t height = 0; // Rows in pixels
+ uint32_t pitch = 0; // Bytes per scanline (>= width * 4)
+ uint32_t col = 0; // Cursor column in character cells
+ uint32_t row = 0; // Cursor row in character cells
+ bool is_bgr = false;
+
+ // C++23 multi-dimensional subscript: direct reference to pixel (x, y).
+ // pitch is in bytes; each pixel is 4 bytes. No bounds checking — callers
+ // must validate coordinates before calling (put_pixel does this).
+ [[nodiscard]] uint32_t& operator[](uint32_t x, uint32_t y) noexcept {
+ auto* row_ptr = reinterpret_cast<uint32_t*>(
+ reinterpret_cast<uintptr_t>(fb) + static_cast<uintptr_t>(y) * pitch);
+ return row_ptr[x];
+ }
+};
+
+static Console g_con{};
+
+// ── Minimal 8x16 bitmap font (ASCII 32–126) ───────────────────────────────
+// Each character is 16 bytes (one byte per row, 8 pixels wide).
+// We store a blank (space) for anything outside the range.
+
+// clang-format off
+static const uint8_t g_font[][16] = {
+ // 32: space
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 33: !
+ {0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
+ // 34: "
+ {0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 35: #
+ {0x00,0x00,0x00,0x6C,0x6C,0xFE,0x6C,0x6C,0xFE,0x6C,0x6C,0x00,0x00,0x00,0x00,0x00},
+ // 36: $
+ {0x00,0x10,0x10,0x7C,0xD6,0xD0,0x7C,0x16,0xD6,0x7C,0x10,0x10,0x00,0x00,0x00,0x00},
+ // 37: %
+ {0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x60,0xC6,0x86,0x00,0x00,0x00,0x00},
+ // 38: &
+ {0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
+ // 39: '
+ {0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 40: (
+ {0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00},
+ // 41: )
+ {0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00},
+ // 42: *
+ {0x00,0x00,0x00,0x00,0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 43: +
+ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 44: ,
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x30,0x00,0x00,0x00},
+ // 45: -
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 46: .
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
+ // 47: /
+ {0x00,0x00,0x00,0x00,0x02,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00},
+ // 48-57: 0-9
+ {0x00,0x00,0x7C,0xC6,0xC6,0xCE,0xDE,0xF6,0xE6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0x06,0x0C,0x18,0x30,0x60,0xC0,0xC6,0xFE,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0x06,0x06,0x3C,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x0C,0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x0C,0x0C,0x1E,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFE,0xC0,0xC0,0xC0,0xFC,0x06,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x38,0x60,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFE,0xC6,0x06,0x06,0x0C,0x18,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7C,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0x7E,0x06,0x06,0x06,0x0C,0x78,0x00,0x00,0x00,0x00},
+ // 58: :
+ {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00},
+ // 59: ;
+ {0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00},
+ // 60: <
+ {0x00,0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00},
+ // 61: =
+ {0x00,0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 62: >
+ {0x00,0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00},
+ // 63: ?
+ {0x00,0x00,0x7C,0xC6,0xC6,0x0C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
+ // 64: @
+ {0x00,0x00,0x7C,0xC6,0xC6,0xDE,0xDE,0xDE,0xDC,0xC0,0xC0,0x7C,0x00,0x00,0x00,0x00},
+ // 65-90: A-Z
+ {0x00,0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x66,0x66,0x66,0x66,0xFC,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xC0,0xC0,0xC2,0x66,0x3C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xF8,0x6C,0x66,0x66,0x66,0x66,0x66,0x66,0x6C,0xF8,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFE,0x66,0x62,0x68,0x78,0x68,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x3C,0x66,0xC2,0xC0,0xC0,0xDE,0xC6,0xC6,0x66,0x3A,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x3C,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x1E,0x0C,0x0C,0x0C,0x0C,0x0C,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xE6,0x66,0x66,0x6C,0x78,0x78,0x6C,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xF0,0x60,0x60,0x60,0x60,0x60,0x60,0x62,0x66,0xFE,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xE6,0xF6,0xFE,0xDE,0xCE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xD6,0xDE,0x7C,0x0C,0x0E,0x00,0x00},
+ {0x00,0x00,0xFC,0x66,0x66,0x66,0x7C,0x6C,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x7C,0xC6,0xC6,0x60,0x38,0x0C,0x06,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFF,0xDB,0x99,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x10,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0xEE,0x6C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xC6,0xC6,0x6C,0x7C,0x38,0x38,0x7C,0x6C,0xC6,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xCC,0xCC,0xCC,0xCC,0x78,0x30,0x30,0x30,0x30,0x78,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xFE,0xC6,0x86,0x0C,0x18,0x30,0x60,0xC2,0xC6,0xFE,0x00,0x00,0x00,0x00},
+ // 91: [
+ {0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00},
+ // 92: backslash
+ {0x00,0x00,0x00,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x02,0x00,0x00,0x00,0x00,0x00},
+ // 93: ]
+ {0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00},
+ // 94: ^
+ {0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 95: _
+ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00},
+ // 96: `
+ {0x00,0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+ // 97-122: a-z
+ {0x00,0x00,0x00,0x00,0x00,0x78,0x0C,0x7C,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0xE0,0x60,0x60,0x78,0x6C,0x66,0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC0,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x1C,0x0C,0x0C,0x3C,0x6C,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xFE,0xC0,0xC0,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x38,0x6C,0x64,0x60,0xF0,0x60,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0xCC,0x78,0x00},
+ {0x00,0x00,0xE0,0x60,0x60,0x6C,0x76,0x66,0x66,0x66,0x66,0xE6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x18,0x18,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x06,0x06,0x00,0x0E,0x06,0x06,0x06,0x06,0x06,0x06,0x66,0x66,0x3C,0x00},
+ {0x00,0x00,0xE0,0x60,0x60,0x66,0x6C,0x78,0x78,0x6C,0x66,0xE6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xEC,0xFE,0xD6,0xD6,0xD6,0xD6,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0xC6,0xC6,0xC6,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xDC,0x66,0x66,0x66,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x76,0xCC,0xCC,0xCC,0xCC,0xCC,0x7C,0x0C,0x0C,0x1E,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xDC,0x76,0x66,0x60,0x60,0x60,0xF0,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0x7C,0xC6,0x60,0x38,0x0C,0xC6,0x7C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x10,0x30,0x30,0xFC,0x30,0x30,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xD6,0xD6,0xD6,0xFE,0x6C,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xC6,0x6C,0x38,0x38,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xC6,0xC6,0x7E,0x06,0x0C,0xF8,0x00},
+ {0x00,0x00,0x00,0x00,0x00,0xFE,0xCC,0x18,0x30,0x60,0xC6,0xFE,0x00,0x00,0x00,0x00},
+ // 123: {
+ {0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00},
+ // 124: |
+ {0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00},
+ // 125: }
+ {0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00},
+ // 126: ~
+ {0x00,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
+};
+// clang-format on
+
+static constexpr int FONT_FIRST = 32;
+static constexpr int FONT_LAST = 126;
+
+// ── Pixel helpers ──────────────────────────────────────────────────────────
+
+static void put_pixel(uint32_t x, uint32_t y, uint32_t rgb) noexcept {
+ if (x >= g_con.width || y >= g_con.height) return;
+
+ // Swap R and B channels if the framebuffer uses BGR layout.
+ const uint32_t color = g_con.is_bgr
+ ? ((rgb & 0xFFu) << 16) | (rgb & 0xFF00u) | ((rgb >> 16) & 0xFFu)
+ : rgb;
+
+ g_con[x, y] = color; // C++23 multi-dimensional subscript
+}
+
+// ── Scroll the screen up by one character row ──────────────────────────────
+
+static void scroll() noexcept {
+ const uint32_t shift = CHAR_H * g_con.pitch;
+ const uint32_t total = g_con.height * g_con.pitch;
+ auto* const fb_bytes = reinterpret_cast<uint8_t*>(g_con.fb);
+
+ // Shift everything up by CHAR_H rows (raw byte memmove equivalent).
+ for (uint32_t i = 0; i < total - shift; ++i)
+ fb_bytes[i] = fb_bytes[i + shift];
+
+ // Fill the newly exposed bottom rows with the background color.
+ for (uint32_t row = g_con.height - CHAR_H; row < g_con.height; ++row)
+ for (uint32_t col = 0; col < g_con.width; ++col)
+ put_pixel(col, row, BG_COLOR);
+}
+
+// ── Render a glyph at character-cell position (col, row) ──────────────────
+
+static void render_char(uint32_t col, uint32_t row, char c) noexcept {
+ // Treat char as unsigned to avoid negative indices for high-ASCII.
+ int idx = static_cast<int>(static_cast<unsigned char>(c)) - FONT_FIRST;
+ if (idx < 0 || idx > (FONT_LAST - FONT_FIRST)) idx = 0; // fall back to space
+
+ const uint32_t px = col * CHAR_W;
+ const uint32_t py = row * CHAR_H;
+
+ for (uint32_t y = 0; y < CHAR_H; ++y) {
+ const uint8_t bits = g_font[idx][y];
+ for (uint32_t x = 0; x < CHAR_W; ++x) {
+ const uint32_t color = (bits & (0x80u >> x)) ? FG_COLOR : BG_COLOR;
+ put_pixel(px + x, py + y, color);
+ }
+ }
+}
+
+// ── Public API ─────────────────────────────────────────────────────────────
+
+void init(const FramebufferInfo& fb) {
+ g_con.fb = reinterpret_cast<uint32_t*>(fb.base);
+ g_con.width = fb.width;
+ g_con.height = fb.height;
+ g_con.pitch = fb.pitch;
+ g_con.is_bgr = (fb.format == PixelFormat::BGR);
+ g_con.col = 0;
+ g_con.row = 0;
+ clear();
+}
+
+void clear() {
+ for (uint32_t y = 0; y < g_con.height; ++y)
+ for (uint32_t x = 0; x < g_con.width; ++x)
+ put_pixel(x, y, BG_COLOR);
+ g_con.col = 0;
+ g_con.row = 0;
+}
+
+void putchar(char c) {
+ const uint32_t max_cols = g_con.width / CHAR_W;
+ const uint32_t max_rows = g_con.height / CHAR_H;
+
+ if (c == '\n') {
+ g_con.col = 0;
+ ++g_con.row;
+ } else if (c == '\r') {
+ g_con.col = 0;
+ } else if (c == '\t') {
+ // Advance to the next TAB_WIDTH-aligned column.
+ g_con.col = (g_con.col + TAB_WIDTH) & ~(TAB_WIDTH - 1u);
+ } else {
+ render_char(g_con.col, g_con.row, c);
+ ++g_con.col;
+ }
+
+ if (g_con.col >= max_cols) {
+ g_con.col = 0;
+ ++g_con.row;
+ }
+
+ if (g_con.row >= max_rows) {
+ scroll();
+ g_con.row = max_rows - 1;
+ }
+}
+
+void puts(const char* s) {
+ if (!s) return;
+ while (*s) putchar(*s++);
+}
+
+void println(const char* s) {
+ puts(s);
+ putchar('\n');
+}
+
+void put_hex(uint64_t val) {
+ puts("0x");
+ bool leading = true;
+ for (int i = 60; i >= 0; i -= 4) {
+ const uint8_t nibble = static_cast<uint8_t>((val >> i) & 0xFu);
+ if (nibble == 0 && leading && i > 0) continue;
+ leading = false;
+ putchar(nibble < 10 ? '0' + nibble : 'A' + nibble - 10);
+ }
+ if (leading) putchar('0');
+}
+
+void put_dec(uint64_t val) {
+ if (val == 0) { putchar('0'); return; }
+ char buf[20];
+ int len = 0;
+ while (val > 0) {
+ buf[len++] = static_cast<char>('0' + val % 10);
+ val /= 10;
+ }
+ for (int j = len - 1; j >= 0; --j) putchar(buf[j]);
+}
+
+void kprintf(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+
+ while (*fmt) {
+ if (*fmt != '%') {
+ putchar(*fmt++);
+ continue;
+ }
+ ++fmt; // skip '%'
+
+ switch (*fmt) {
+ case 's': {
+ const char* s = va_arg(args, const char*);
+ puts(s ? s : "(null)");
+ break;
+ }
+ case 'd': {
+ int64_t v = va_arg(args, int64_t);
+ if (v < 0) { putchar('-'); v = -v; }
+ put_dec(static_cast<uint64_t>(v));
+ break;
+ }
+ case 'u': {
+ put_dec(va_arg(args, uint64_t));
+ break;
+ }
+ case 'x':
+ case 'p': {
+ put_hex(va_arg(args, uint64_t));
+ break;
+ }
+ case '%':
+ putchar('%');
+ break;
+ case '\0':
+ goto done;
+ default:
+ putchar('%');
+ putchar(*fmt);
+ break;
+ }
+ ++fmt;
+ }
+done:
+ va_end(args);
+}
+
+} // namespace kcon