84 lines
3.4 KiB
Zig
84 lines
3.4 KiB
Zig
//! 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
|
|
}
|