From e1561995d4064847de60daf5342f8e7ff69e7e30 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Sat, 2 May 2026 01:32:13 +0800 Subject: feat: implement kernel logging and formatted output subsystems --- na/include/lume/klog.h | 167 +++++++++++++++ na/include/lume/kprintf.h | 39 ++++ na/include/lume/panic.h | 16 -- na/include/lume/types.h | 5 +- na/lib/klog.cc | 518 ++++++++++++++++++++++++++++++++++++++++++++++ na/lib/kprintf.cc | 68 ++++++ na/lib/panic.cc | 34 --- 7 files changed, 796 insertions(+), 51 deletions(-) create mode 100644 na/include/lume/klog.h create mode 100644 na/include/lume/kprintf.h delete mode 100644 na/include/lume/panic.h create mode 100644 na/lib/klog.cc create mode 100644 na/lib/kprintf.cc delete mode 100644 na/lib/panic.cc 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 + +/* ======================================================================== + * 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 + +#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 +#include +#include +#include +#include +#include + +/* 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 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(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(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(hdr.level)); + const char* subsys = klog_subsys_name(static_cast(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(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(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(msg_len); + hdr.ts = arch::cpu::read_time(); + hdr.cpu = static_cast(arch::cpu::id()); + hdr.level = static_cast(level); + hdr.subsys = static_cast(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(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 +#include +#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 -#include -#include -#include - -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(); - } -} -- cgit v1.2.3