summaryrefslogtreecommitdiff
path: root/lib/klog.cc
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
commite90b72ec3082588b296956730bd2930cf8d5d671 (patch)
tree36ff6916af9b4176ea5a9662219d11e721f0b691 /lib/klog.cc
parent1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff)
downloadlume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz
lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'lib/klog.cc')
-rw-r--r--lib/klog.cc574
1 files changed, 574 insertions, 0 deletions
diff --git a/lib/klog.cc b/lib/klog.cc
new file mode 100644
index 0000000..86c67f8
--- /dev/null
+++ b/lib/klog.cc
@@ -0,0 +1,574 @@
+/* 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 "?";
+}
+
+static const char* parse_kern_level(const char* fmt, KlogLevel* level)
+{
+ if (!fmt || !level) {
+ return fmt;
+ }
+
+ /* Linux-style "\001n" prefix (KERN_SOH) */
+ if (fmt[0] == '\001' && fmt[1] >= '0' && fmt[1] <= '7') {
+ *level = static_cast<KlogLevel>(fmt[1] - '0');
+ return fmt + 2;
+ }
+
+ /* Alternate "<n>" prefix */
+ if (fmt[0] == '<' && fmt[1] >= '0' && fmt[1] <= '7' && fmt[2] == '>') {
+ *level = static_cast<KlogLevel>(fmt[1] - '0');
+ return fmt + 3;
+ }
+
+ return fmt;
+}
+
+/* ========================================================================
+ * 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)
+{
+ console_write_all(s, len);
+}
+
+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;
+ /* Emit to UART while holding the lock to prevent
+ * interleaved output from concurrent CPUs. */
+ 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);
+ }
+ }
+ } else if (do_console) {
+ /* Pre-init or panic mode: emit without lock */
+ 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);
+ }
+ }
+}
+
+int vprintk(const char* fmt, __builtin_va_list va)
+{
+ KlogLevel level = KLOG_INFO;
+ const char* real_fmt = parse_kern_level(fmt, &level);
+
+ __builtin_va_list va_copy;
+ __builtin_va_copy(va_copy, va);
+
+ char tmp[KLOG_LINE_MAX + 1];
+ int len = vsnprintf_(tmp, KLOG_LINE_MAX, real_fmt, va_copy);
+ __builtin_va_end(va_copy);
+
+ klog_vex(level, KLOG_SUBSYS_GENERIC, real_fmt, va);
+
+ if (len < 0) {
+ return 0;
+ }
+ return len;
+}
+
+int printk(const char* fmt, ...)
+{
+ __builtin_va_list va;
+ __builtin_va_start(va, fmt);
+ int ret = vprintk(fmt, va);
+ __builtin_va_end(va);
+ return ret;
+}
+
+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();
+ }
+}