//! The kernel's multi-sink **diagnostic** log — the machine-readable stream of //! what the kernel is doing, separate from any user-facing display. //! //! Output is a *diagnostic convenience, never a correctness dependency* — the //! kernel must boot and run correctly with zero output channels. So logging fans //! out to a set of registered **sinks**, each best-effort and self-guarding: the //! serial UART, the 0xE9 debug console, and — later — a file on a ramdisk/USB/SSD. //! A message reaches whatever channels exist; if none do, the kernel runs on, //! silent but correct. //! //! The **framebuffer is deliberately not a sink here.** It's a separate output //! surface (a bootstrap text console today, a graphics device driver later), so //! the log never assumes the machine is text-based. `main.zig` mirrors a few //! user-facing status lines and panics to it explicitly; the verbose log does not. //! //! No allocation: the sink table is fixed, so the log works before the heap is up //! and inside a panic. Two channels don't go through the sink list because they //! must survive even a total-output failure: `checkpoint` (a one-byte POST code) //! and `recordPanic` (a breadcrumb in a fixed record). const std = @import("std"); const architecture = @import("architecture"); pub const SinkFn = *const fn ([]const u8) void; const maximum_sinks = 8; var sinks: [maximum_sinks]SinkFn = undefined; var sink_count: usize = 0; /// Register an output sink. Every registered sink receives every message; sinks /// must be self-guarding (safe to call when their device is absent). pub fn addSink(sink: SinkFn) void { if (sink_count < maximum_sinks) { sinks[sink_count] = sink; sink_count += 1; } } /// Fan `bytes` out to every registered sink. pub fn write(bytes: []const u8) void { for (sinks[0..sink_count]) |sink| sink(bytes); } /// A formatted log line. Truncates past 256 bytes; the buffer is on the stack, so /// this is safe to call from interrupt context and from a panic. pub fn print(comptime fmt: []const u8, args: anytype) void { var buffer: [256]u8 = undefined; write(std.fmt.bufPrint(&buffer, fmt, args) catch return); } /// Emit a one-byte checkpoint/POST code (I/O port 0x80) — the always-available /// progress channel for when there is no text output at all. Independent of the /// sink list, so it works even before any sink is registered. pub fn checkpoint(code: u8) void { architecture.checkpoint(code); } // --- persistent panic breadcrumb ------------------------------------------- // // A fixed record in the kernel image that a panic fills in, so a post-mortem — an // attached debugger, a RAM dump, or (later) a file/pstore reader — can recover // what killed the kernel even when there was no live console. `magic` is written // *last*, so a reader only trusts a fully-written record. pub const panic_magic: u64 = 0xD1ED_B00B_5EED_F00D; pub const PanicRecord = extern struct { magic: u64 = 0, len: u32 = 0, _pad: u32 = 0, message: [512]u8 = undefined, }; /// Findable by symbol (`log.panic_record`) for a debugger or RAM dump. pub var panic_record: PanicRecord = .{}; /// Stamp the panic message into the breadcrumb record. pub fn recordPanic(message: []const u8) void { const n: u32 = @intCast(@min(message.len, panic_record.message.len)); @memcpy(panic_record.message[0..n], message[0..n]); panic_record.len = n; panic_record.magic = panic_magic; // set last: a reader sees a complete record }