diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:31:46 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:31:46 +0800 |
| commit | e90b72ec3082588b296956730bd2930cf8d5d671 (patch) | |
| tree | 36ff6916af9b4176ea5a9662219d11e721f0b691 /lib/klog.cc | |
| parent | 1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff) | |
| download | lume-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.cc | 574 |
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(); + } +} |
