summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--na/include/lume/klog.h167
-rw-r--r--na/include/lume/kprintf.h39
-rw-r--r--na/include/lume/panic.h16
-rw-r--r--na/include/lume/types.h5
-rw-r--r--na/lib/klog.cc518
-rw-r--r--na/lib/kprintf.cc68
-rw-r--r--na/lib/panic.cc34
7 files changed, 796 insertions, 51 deletions
diff --git a/na/include/lume/klog.h b/na/include/lume/klog.h
new file mode 100644
index 0000000..457dcfe
--- /dev/null
+++ b/na/include/lume/klog.h
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Kernel Logging Subsystem
+ *
+ * All kernel output goes through klog(), which formats messages into
+ * a fixed-size ring buffer and optionally emits them to the console.
+ *
+ * Architecture:
+ * vsnprintf_() ← pure formatting (mpaland/printf)
+ * ↓
+ * KlogBuffer (ring) ← all messages stored here
+ * ↓
+ * early_putc() (sync) ← current: synchronous UART output
+ * klogd thread (async) ← future: background flush thread
+ * /proc/kmsg ← future: userspace dmesg interface
+ *
+ * Reference: docs/specs/klog.md
+ */
+
+#include <lume/types.h>
+
+/* ========================================================================
+ * Log Levels (matches Linux kern_levels.h ordering)
+ * ======================================================================== */
+
+enum KlogLevel : uint8 {
+ KLOG_EMERG = 0, /* System is unusable */
+ KLOG_ALERT = 1, /* Action must be taken immediately */
+ KLOG_CRIT = 2, /* Critical conditions */
+ KLOG_ERR = 3, /* Error conditions */
+ KLOG_WARN = 4, /* Warning conditions */
+ KLOG_NOTICE = 5, /* Normal but significant condition */
+ KLOG_INFO = 6, /* Informational */
+ KLOG_DEBUG = 7, /* Debug-level messages */
+};
+
+/* Log subsystems (bitmask-friendly) */
+enum KlogSubsys : uint8 {
+ KLOG_SUBSYS_GENERIC = 0,
+ KLOG_SUBSYS_SCHED = 1,
+ KLOG_SUBSYS_TRAP = 2,
+ KLOG_SUBSYS_IRQ = 3,
+ KLOG_SUBSYS_TIMER = 4,
+ KLOG_SUBSYS_VMM = 5,
+ KLOG_SUBSYS_PMM = 6,
+ KLOG_SUBSYS_SLAB = 7,
+ KLOG_SUBSYS_FS = 8,
+ KLOG_SUBSYS_DRIVER = 9,
+ KLOG_SUBSYS_TEST = 10,
+ KLOG_SUBSYS_MAX = 11,
+};
+
+/* Default: show everything up to INFO. DEBUG is compile-gated. */
+#ifndef KLOG_DEFAULT_LEVEL
+#define KLOG_DEFAULT_LEVEL KLOG_INFO
+#endif
+
+/* Ring buffer size: 2^KLOG_BUF_SHIFT bytes (default 64KB) */
+#ifndef KLOG_BUF_SHIFT
+#define KLOG_BUF_SHIFT 16
+#endif
+
+#define KLOG_BUF_SIZE (1U << KLOG_BUF_SHIFT)
+
+/* Maximum length of a single log message (stack-allocated) */
+#define KLOG_LINE_MAX 256
+
+struct KlogStats {
+ uint32 head;
+ uint32 tail;
+ uint32 console_tail;
+ uint32 dropped;
+ uint32 seq;
+};
+
+struct KlogRateLimit {
+ uint64 last_ts;
+ uint32 interval;
+ uint32 burst;
+ uint32 printed;
+ uint32 suppressed;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ========================================================================
+ * Initialization
+ * ======================================================================== */
+
+/* Initialize the ring buffer and console backend. Call once from BSP. */
+void klog_init(void);
+
+/* ========================================================================
+ * Core API
+ * ======================================================================== */
+
+/* Write a leveled log message. Formats into ring buffer, then
+ * synchronously flushes to console if level <= current threshold. */
+__attribute__((format(printf, 2, 3)))
+void klog(KlogLevel level, const char* fmt, ...);
+
+__attribute__((format(printf, 3, 4)))
+void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...);
+
+/* Set runtime log level filter. Messages above this level are
+ * still stored in the ring buffer but not printed to console. */
+void klog_set_level(KlogLevel level);
+
+void klog_set_subsys_mask(uint64 mask);
+void klog_enable_subsys(KlogSubsys subsys);
+void klog_disable_subsys(KlogSubsys subsys);
+
+/* ========================================================================
+ * Ring Buffer Read (for future dmesg / /proc/kmsg)
+ * ======================================================================== */
+
+/* Read up to 'size' bytes from the ring buffer into 'buf'.
+ * Returns number of bytes read. Non-destructive peek. */
+int klog_read(char* buf, int size);
+
+/* Read and consume entries (advances tail). */
+int klog_read_consume(char* buf, int size);
+
+/* Flush pending entries to console (advances console tail). */
+void klog_flush(void);
+
+bool klog_ratelimit(KlogRateLimit* rl, uint64 now);
+
+void klog_stats(KlogStats* out);
+
+/* ========================================================================
+ * Convenience Macros
+ * ======================================================================== */
+
+#define pr_emerg(fmt, ...) klog_ex(KLOG_EMERG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#define pr_alert(fmt, ...) klog_ex(KLOG_ALERT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#define pr_crit(fmt, ...) klog_ex(KLOG_CRIT, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#define pr_err(fmt, ...) klog_ex(KLOG_ERR, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#define pr_warn(fmt, ...) klog_ex(KLOG_WARN, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#define pr_notice(fmt, ...) klog_ex(KLOG_NOTICE, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#define pr_info(fmt, ...) klog_ex(KLOG_INFO, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+
+#define pr_emerg_s(subsys, fmt, ...) klog_ex(KLOG_EMERG, subsys, fmt, ##__VA_ARGS__)
+#define pr_alert_s(subsys, fmt, ...) klog_ex(KLOG_ALERT, subsys, fmt, ##__VA_ARGS__)
+#define pr_crit_s(subsys, fmt, ...) klog_ex(KLOG_CRIT, subsys, fmt, ##__VA_ARGS__)
+#define pr_err_s(subsys, fmt, ...) klog_ex(KLOG_ERR, subsys, fmt, ##__VA_ARGS__)
+#define pr_warn_s(subsys, fmt, ...) klog_ex(KLOG_WARN, subsys, fmt, ##__VA_ARGS__)
+#define pr_notice_s(subsys, fmt, ...) klog_ex(KLOG_NOTICE, subsys, fmt, ##__VA_ARGS__)
+#define pr_info_s(subsys, fmt, ...) klog_ex(KLOG_INFO, subsys, fmt, ##__VA_ARGS__)
+
+#ifdef KLOG_ENABLE_DEBUG
+#define pr_debug(fmt, ...) klog_ex(KLOG_DEBUG, KLOG_SUBSYS_GENERIC, fmt, ##__VA_ARGS__)
+#else
+#define pr_debug(fmt, ...) do {} while (0)
+#endif
+
+extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/na/include/lume/kprintf.h b/na/include/lume/kprintf.h
new file mode 100644
index 0000000..b158602
--- /dev/null
+++ b/na/include/lume/kprintf.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Kernel Formatted Output — kprintf
+ *
+ * Provides printf-style formatted output for the kernel.
+ * Built on top of stb_sprintf for full format string support
+ * without any libc dependency.
+ *
+ * Output is synchronous and directed to the console (UART).
+ * Safe to call from any context (interrupt or process).
+ *
+ * Usage:
+ * kprintf("Hello %s, addr=0x%lx\n", name, addr);
+ */
+
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Formatted print to console (like printf). */
+__attribute__((format(printf, 1, 2)))
+int kprintf(const char* fmt, ...);
+
+/* Formatted print with va_list. */
+int kvprintf(const char* fmt, va_list va);
+
+/* Formatted snprintf into buffer. */
+__attribute__((format(printf, 3, 4)))
+int ksnprintf(char* buf, int count, const char* fmt, ...);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/na/include/lume/panic.h b/na/include/lume/panic.h
deleted file mode 100644
index 6316c28..0000000
--- a/na/include/lume/panic.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-#pragma once
-
-/*
- * Kernel Panic — fatal error handler.
- *
- * Prints the message (if UART is ready), disables interrupts,
- * and enters an infinite wfi loop. Never returns.
- *
- * Implementation will be provided by lib/kprintf.cc or a
- * dedicated panic module. For now this header declares the interface.
- */
-
-extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail = nullptr);
diff --git a/na/include/lume/types.h b/na/include/lume/types.h
index e296649..1d5b896 100644
--- a/na/include/lume/types.h
+++ b/na/include/lume/types.h
@@ -22,10 +22,13 @@ using int16 = signed short;
using int32 = signed int;
using int64 = signed long long;
-using size_t = uint64;
+typedef __SIZE_TYPE__ size_t;
+typedef __PTRDIFF_TYPE__ ptrdiff_t;
using ssize_t = int64;
using uintptr = uint64;
+#define offsetof(TYPE, MEMBER) __builtin_offsetof(TYPE, MEMBER)
+
/* Null pointer constant */
#ifndef nullptr
/* C++11 and later have nullptr built-in */
diff --git a/na/lib/klog.cc b/na/lib/klog.cc
new file mode 100644
index 0000000..c67971b
--- /dev/null
+++ b/na/lib/klog.cc
@@ -0,0 +1,518 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * klog.cc — Kernel logging subsystem implementation
+ *
+ * Implements the ring buffer, spinlock-protected writes, log level
+ * filtering, and synchronous console output.
+ *
+ * Phase 2 TODO: async flush via kernel thread (see docs/specs/klog.md)
+ */
+
+#include <lume/klog.h>
+#include <lume/kprintf.h>
+#include <lume/console.h>
+#include <lume/atomic.h>
+#include <kernel/sync/spinlock.h>
+#include <arch/cpu.h>
+
+/* Forward declaration — mpaland/printf */
+extern "C" int vsnprintf_(char* buffer, size_t count,
+ const char* format, __builtin_va_list va);
+
+/* ========================================================================
+ * Ring Buffer State
+ * ======================================================================== */
+
+struct KlogEntryHdr {
+ uint32 seq;
+ uint32 len;
+ uint64 ts;
+ uint16 cpu;
+ uint8 level;
+ uint8 subsys;
+ uint16 reserved;
+};
+
+static struct {
+ char data[KLOG_BUF_SIZE];
+ uint32 head; /* Next write position */
+ uint32 tail; /* Next read position (for /proc/kmsg) */
+ uint32 console_tail; /* Next position to flush to console */
+ uint32 dropped;
+ lume::atomic<uint32> seq;
+ uint64 subsys_mask;
+ Spinlock lock;
+ KlogLevel console_level; /* Messages <= this level go to UART */
+ bool initialized;
+ bool panic_mode;
+} g_klog;
+
+/* ========================================================================
+ * Level name table
+ * ======================================================================== */
+
+static const char* klog_level_name(KlogLevel level)
+{
+ static const char* names[] = {
+ "EMERG", "ALERT", "CRIT", "ERR",
+ "WARN", "NOTICE", "INFO", "DEBUG"
+ };
+ if (level <= KLOG_DEBUG) {
+ return names[level];
+ }
+ return "?";
+}
+
+static const char* klog_subsys_name(KlogSubsys subsys)
+{
+ static const char* names[] = {
+ "GEN",
+ "SCHED",
+ "TRAP",
+ "IRQ",
+ "TIMER",
+ "VMM",
+ "PMM",
+ "SLAB",
+ "FS",
+ "DRV",
+ "TEST",
+ };
+ if (subsys < KLOG_SUBSYS_MAX) {
+ return names[subsys];
+ }
+ return "?";
+}
+
+/* ========================================================================
+ * Ring Buffer Operations
+ * ======================================================================== */
+
+/* Write a byte to the ring buffer. Overwrites oldest data on overflow. */
+static inline uint32 min_u32(uint32 a, uint32 b)
+{
+ return (a < b) ? a : b;
+}
+
+static void ring_write_bytes(const void* src, uint32 len)
+{
+ const char* p = reinterpret_cast<const char*>(src);
+ for (uint32 i = 0; i < len; ++i) {
+ g_klog.data[(g_klog.head + i) & (KLOG_BUF_SIZE - 1)] = p[i];
+ }
+ g_klog.head += len;
+}
+
+static void ring_read_bytes(uint32 pos, void* dst, uint32 len)
+{
+ char* out = reinterpret_cast<char*>(dst);
+ for (uint32 i = 0; i < len; ++i) {
+ out[i] = g_klog.data[(pos + i) & (KLOG_BUF_SIZE - 1)];
+ }
+}
+
+static void ring_drop_oldest(uint32 needed)
+{
+ uint32 min_tail = min_u32(g_klog.tail, g_klog.console_tail);
+
+ while (KLOG_BUF_SIZE - (g_klog.head - min_tail) < needed) {
+ KlogEntryHdr hdr;
+ ring_read_bytes(min_tail, &hdr, sizeof(hdr));
+
+ uint32 entry = sizeof(hdr) + hdr.len;
+ if (entry == 0 || entry > KLOG_BUF_SIZE) {
+ min_tail++;
+ g_klog.dropped++;
+ continue;
+ }
+ min_tail += entry;
+ g_klog.dropped++;
+ }
+
+ if (g_klog.tail < min_tail) {
+ g_klog.tail = min_tail;
+ }
+ if (g_klog.console_tail < min_tail) {
+ g_klog.console_tail = min_tail;
+ }
+}
+
+/* ========================================================================
+ * Console Output (synchronous)
+ * ======================================================================== */
+
+static void console_emit(const char* s, int len)
+{
+ for (int i = 0; i < len; ++i) {
+ early_putc(s[i]);
+ }
+}
+
+static int klog_format_line(char* out, int out_size,
+ const KlogEntryHdr& hdr,
+ const char* msg, int msg_len)
+{
+ const char* lvl = klog_level_name(static_cast<KlogLevel>(hdr.level));
+ const char* subsys = klog_subsys_name(static_cast<KlogSubsys>(hdr.subsys));
+
+ int prefix = ksnprintf(out, out_size,
+ "[%s][cpu%u][%llu][%s] ",
+ lvl, (uint32)hdr.cpu,
+ (unsigned long long)hdr.ts, subsys);
+ if (prefix < 0) {
+ return 0;
+ }
+
+ int total = prefix;
+ int avail = out_size - 1 - total;
+ if (avail > 0) {
+ int to_copy = (msg_len < avail) ? msg_len : avail;
+ for (int i = 0; i < to_copy; ++i) {
+ out[total + i] = msg[i];
+ }
+ total += to_copy;
+ }
+
+ if (total > 0 && out[total - 1] != '\n' && total < out_size - 1) {
+ out[total++] = '\n';
+ }
+
+ out[total] = '\0';
+ return total;
+}
+
+static int klog_read_internal(char* buf, int size, uint32* tail_ptr, bool consume)
+{
+ if (size <= 0) {
+ return 0;
+ }
+
+ int written = 0;
+ uint32 pos = *tail_ptr;
+
+ while (pos != g_klog.head) {
+ KlogEntryHdr hdr;
+ ring_read_bytes(pos, &hdr, sizeof(hdr));
+ uint32 raw_len = hdr.len;
+ if (raw_len > KLOG_BUF_SIZE) {
+ break;
+ }
+ uint32 copy_len = raw_len;
+ if (copy_len > KLOG_LINE_MAX) {
+ copy_len = KLOG_LINE_MAX;
+ }
+
+ char msg[KLOG_LINE_MAX + 1];
+ ring_read_bytes(pos + sizeof(hdr), msg, copy_len);
+ msg[copy_len] = '\0';
+
+ char line[KLOG_LINE_MAX + 64];
+ int line_len = klog_format_line(line, sizeof(line), hdr, msg,
+ static_cast<int>(copy_len));
+
+ if (line_len <= 0 || written + line_len > size) {
+ break;
+ }
+
+ for (int i = 0; i < line_len; ++i) {
+ buf[written + i] = line[i];
+ }
+ written += line_len;
+
+ pos += sizeof(hdr) + raw_len;
+ }
+
+ if (consume) {
+ *tail_ptr = pos;
+ }
+
+ return written;
+}
+
+static void klog_vex(KlogLevel level, KlogSubsys subsys,
+ const char* fmt, __builtin_va_list va);
+
+/* ========================================================================
+ * Public API
+ * ======================================================================== */
+
+void klog_init(void)
+{
+ g_klog.head = 0;
+ g_klog.tail = 0;
+ g_klog.console_tail = 0;
+ g_klog.dropped = 0;
+ g_klog.seq.store(0, __ATOMIC_RELAXED);
+ g_klog.subsys_mask = (1ULL << KLOG_SUBSYS_MAX) - 1;
+ g_klog.console_level = static_cast<KlogLevel>(KLOG_DEFAULT_LEVEL);
+ g_klog.initialized = true;
+ g_klog.panic_mode = false;
+ g_klog.lock.init("klog");
+}
+
+void klog(KlogLevel level, const char* fmt, ...)
+{
+ __builtin_va_list va;
+ __builtin_va_start(va, fmt);
+ klog_vex(level, KLOG_SUBSYS_GENERIC, fmt, va);
+ __builtin_va_end(va);
+}
+
+void klog_ex(KlogLevel level, KlogSubsys subsys, const char* fmt, ...)
+{
+ __builtin_va_list va;
+ __builtin_va_start(va, fmt);
+ klog_vex(level, subsys, fmt, va);
+ __builtin_va_end(va);
+}
+
+static void klog_vex(KlogLevel level, KlogSubsys subsys,
+ const char* fmt, __builtin_va_list va)
+{
+ if (subsys >= KLOG_SUBSYS_MAX) {
+ subsys = KLOG_SUBSYS_GENERIC;
+ }
+
+ if (g_klog.initialized && ((g_klog.subsys_mask & (1ULL << subsys)) == 0)) {
+ return;
+ }
+
+ char msg[KLOG_LINE_MAX + 1];
+ int msg_len = vsnprintf_(msg, KLOG_LINE_MAX, fmt, va);
+
+ if (msg_len < 0) {
+ return;
+ }
+ if (msg_len > KLOG_LINE_MAX) {
+ msg_len = KLOG_LINE_MAX;
+ }
+ msg[msg_len] = '\0';
+
+ KlogEntryHdr hdr;
+ hdr.seq = g_klog.seq.fetch_add(1, __ATOMIC_RELAXED);
+ hdr.len = static_cast<uint32>(msg_len);
+ hdr.ts = arch::cpu::read_time();
+ hdr.cpu = static_cast<uint16>(arch::cpu::id());
+ hdr.level = static_cast<uint8>(level);
+ hdr.subsys = static_cast<uint8>(subsys);
+ hdr.reserved = 0;
+
+ bool do_console = (!g_klog.initialized || level <= g_klog.console_level);
+
+ if (g_klog.initialized && !g_klog.panic_mode) {
+ LockGuard guard(g_klog.lock);
+ ring_drop_oldest(sizeof(hdr) + hdr.len);
+ ring_write_bytes(&hdr, sizeof(hdr));
+ ring_write_bytes(msg, hdr.len);
+ if (do_console) {
+ g_klog.console_tail = g_klog.head;
+ }
+ }
+
+ if (do_console) {
+ char line[KLOG_LINE_MAX + 64];
+ int line_len = klog_format_line(line, sizeof(line), hdr, msg, hdr.len);
+ if (line_len > 0) {
+ console_emit(line, line_len);
+ }
+ }
+}
+
+void klog_set_level(KlogLevel level)
+{
+ g_klog.console_level = level;
+}
+
+void klog_set_subsys_mask(uint64 mask)
+{
+ g_klog.subsys_mask = mask;
+}
+
+void klog_enable_subsys(KlogSubsys subsys)
+{
+ if (subsys < KLOG_SUBSYS_MAX) {
+ g_klog.subsys_mask |= (1ULL << subsys);
+ }
+}
+
+void klog_disable_subsys(KlogSubsys subsys)
+{
+ if (subsys < KLOG_SUBSYS_MAX) {
+ g_klog.subsys_mask &= ~(1ULL << subsys);
+ }
+}
+
+int klog_read(char* buf, int size)
+{
+ if (!g_klog.initialized || size <= 0) {
+ return 0;
+ }
+
+ LockGuard guard(g_klog.lock);
+ return klog_read_internal(buf, size, &g_klog.tail, false);
+}
+
+int klog_read_consume(char* buf, int size)
+{
+ if (!g_klog.initialized || size <= 0) {
+ return 0;
+ }
+
+ LockGuard guard(g_klog.lock);
+ return klog_read_internal(buf, size, &g_klog.tail, true);
+}
+
+void klog_flush(void)
+{
+ if (!g_klog.initialized) {
+ return;
+ }
+
+ LockGuard guard(g_klog.lock);
+
+ uint32 pos = g_klog.console_tail;
+ while (pos != g_klog.head) {
+ KlogEntryHdr hdr;
+ ring_read_bytes(pos, &hdr, sizeof(hdr));
+ uint32 raw_len = hdr.len;
+ if (raw_len > KLOG_BUF_SIZE) {
+ break;
+ }
+ uint32 copy_len = raw_len;
+ if (copy_len > KLOG_LINE_MAX) {
+ copy_len = KLOG_LINE_MAX;
+ }
+
+ char msg[KLOG_LINE_MAX + 1];
+ ring_read_bytes(pos + sizeof(hdr), msg, copy_len);
+ msg[copy_len] = '\0';
+
+ char line[KLOG_LINE_MAX + 64];
+ int line_len = klog_format_line(line, sizeof(line), hdr, msg,
+ static_cast<int>(copy_len));
+ if (line_len > 0) {
+ console_emit(line, line_len);
+ }
+
+ pos += sizeof(hdr) + raw_len;
+ }
+
+ g_klog.console_tail = pos;
+}
+
+bool klog_ratelimit(KlogRateLimit* rl, uint64 now)
+{
+ if (!rl) {
+ return true;
+ }
+
+ if (rl->interval == 0 || rl->burst == 0) {
+ return true;
+ }
+
+ if (now - rl->last_ts >= rl->interval) {
+ rl->last_ts = now;
+ rl->printed = 0;
+ rl->suppressed = 0;
+ }
+
+ if (rl->printed < rl->burst) {
+ rl->printed++;
+ return true;
+ }
+
+ rl->suppressed++;
+ return false;
+}
+
+void klog_stats(KlogStats* out)
+{
+ if (!out) {
+ return;
+ }
+
+ out->head = g_klog.head;
+ out->tail = g_klog.tail;
+ out->console_tail = g_klog.console_tail;
+ out->dropped = g_klog.dropped;
+ out->seq = g_klog.seq.load(__ATOMIC_RELAXED);
+}
+
+/* ========================================================================
+ * Kernel Panic — Fatal Error Handler
+ *
+ * Dumps all critical S-mode CSRs and the stack pointer to the console,
+ * then halts the CPU. Uses kprintf (NOT klog) to avoid deadlocking
+ * if the panic was triggered while holding the klog spinlock.
+ * ======================================================================== */
+
+extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail) {
+ /* Disable interrupts immediately */
+ arch::cpu::intr_off();
+
+ g_klog.panic_mode = true;
+
+ /* ---- Banner ---- */
+ kprintf("\n\n");
+ kprintf("========================================\n");
+ kprintf(" *** KERNEL PANIC ***\n");
+ kprintf("========================================\n");
+
+ if (msg != nullptr) {
+ kprintf(" Reason : %s", msg);
+ if (detail != nullptr) {
+ kprintf(": %s", detail);
+ }
+ kprintf("\n");
+ }
+
+ /* ---- Read S-mode CSRs ---- */
+ uint64 sepc, scause, stval, sstatus, satp, stvec, sscratch, sp_val;
+
+ __asm__ volatile("csrr %0, sepc" : "=r"(sepc));
+ __asm__ volatile("csrr %0, scause" : "=r"(scause));
+ __asm__ volatile("csrr %0, stval" : "=r"(stval));
+ __asm__ volatile("csrr %0, sstatus" : "=r"(sstatus));
+ __asm__ volatile("csrr %0, satp" : "=r"(satp));
+ __asm__ volatile("csrr %0, stvec" : "=r"(stvec));
+ __asm__ volatile("csrr %0, sscratch" : "=r"(sscratch));
+ __asm__ volatile("mv %0, sp" : "=r"(sp_val));
+
+ kprintf("\n--- S-mode CSR Dump ---\n");
+ kprintf(" sepc = 0x%016llx\n", sepc);
+ kprintf(" scause = 0x%016llx", scause);
+
+ /* Decode scause for convenience */
+ bool is_interrupt = (scause >> 63) != 0;
+ uint64 cause_code = scause & 0x7FFFFFFFFFFFFFFFULL;
+ if (is_interrupt) {
+ kprintf(" (interrupt, code=%llu)\n", cause_code);
+ } else {
+ kprintf(" (exception, code=%llu)\n", cause_code);
+ }
+
+ kprintf(" stval = 0x%016llx\n", stval);
+ kprintf(" sstatus = 0x%016llx", sstatus);
+ kprintf(" [SIE=%llu SPP=%llu SPIE=%llu]\n",
+ (sstatus >> 1) & 1, /* SIE */
+ (sstatus >> 8) & 1, /* SPP */
+ (sstatus >> 5) & 1); /* SPIE */
+ kprintf(" satp = 0x%016llx", satp);
+ uint64 satp_mode = satp >> 60;
+ kprintf(" [mode=%llu]\n", satp_mode);
+ kprintf(" stvec = 0x%016llx\n", stvec);
+ kprintf(" sscratch = 0x%016llx\n", sscratch);
+ kprintf(" sp = 0x%016llx\n", sp_val);
+
+ kprintf(" cpu_id = %llu\n", arch::cpu::id());
+ kprintf(" time = %llu\n", arch::cpu::read_time());
+
+ kprintf("========================================\n\n");
+
+ /* Halt — spin forever with WFI to save power */
+ while (true) {
+ arch::cpu::halt_until_interrupt();
+ }
+}
diff --git a/na/lib/kprintf.cc b/na/lib/kprintf.cc
new file mode 100644
index 0000000..ddd2e59
--- /dev/null
+++ b/na/lib/kprintf.cc
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * kprintf.cc — Kernel formatted output
+ *
+ * Uses mpaland/printf's vsnprintf_ as the core formatting engine.
+ * All kernel output ultimately goes through this module.
+ *
+ * Architecture:
+ * vsnprintf_() ← pure formatting, writes to buffer
+ * ↓
+ * kprintf() ← format + emit to UART via early_putc
+ * kvprintf() ← va_list variant
+ * ksnprintf() ← format into caller's buffer
+ */
+
+#include <lume/kprintf.h>
+#include <lume/console.h>
+#include "printf.h"
+
+/* ------------------------------------------------------------------ */
+/* _putchar: required by mpaland/printf for printf_() / vprintf_() */
+/* ------------------------------------------------------------------ */
+
+void _putchar(char character)
+{
+ early_putc(character);
+}
+
+/* ------------------------------------------------------------------ */
+/* Public API */
+/* ------------------------------------------------------------------ */
+
+extern "C" int kprintf(const char* fmt, ...)
+{
+ char buf[256];
+ va_list va;
+ va_start(va, fmt);
+ int len = vsnprintf_(buf, sizeof(buf), fmt, va);
+ va_end(va);
+
+ /* Emit to console */
+ for (int i = 0; i < len && buf[i] != '\0'; ++i) {
+ early_putc(buf[i]);
+ }
+ return len;
+}
+
+extern "C" int kvprintf(const char* fmt, va_list va)
+{
+ char buf[256];
+ int len = vsnprintf_(buf, sizeof(buf), fmt, va);
+
+ for (int i = 0; i < len && buf[i] != '\0'; ++i) {
+ early_putc(buf[i]);
+ }
+ return len;
+}
+
+extern "C" int ksnprintf(char* buf, int count, const char* fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ int ret = vsnprintf_(buf, (size_t)count, fmt, va);
+ va_end(va);
+ return ret;
+}
diff --git a/na/lib/panic.cc b/na/lib/panic.cc
deleted file mode 100644
index 753f910..0000000
--- a/na/lib/panic.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/* Copyright (C) 2026 Richard Qin */
-
-/*
- * Kernel Panic — minimal fatal error handler.
- *
- * Before console_init(): silent death (no UART).
- * After console_init(): prints message then halts.
- *
- * Reference: docs/specs/global_init.md §6
- */
-
-#include <lume/types.h>
-#include <lume/addr.h>
-#include <arch/cpu.h>
-#include <lume/console.h>
-
-extern "C" [[noreturn]] void kernel_panic(const char* msg, const char* detail) {
- arch::cpu::intr_off();
-
- early_puts("\n*** KERNEL PANIC: ");
- if (msg) {
- early_puts(msg);
- }
- if (detail) {
- early_puts(": ");
- early_puts(detail);
- }
- early_puts(" ***\n");
-
- while (true) {
- arch::cpu::halt_until_interrupt();
- }
-}