The source layout now mirrors the runtime filesystem hierarchy
(docs/danos-file-system-hierarchy-FSH.md): what lives under system/ in the
source is what a running danos represents under /system. Each service and
driver is a sub-project directory that is its own Zig module — cross-project
references go by module name, never by a path into another project's files.
Moves (all git mv, history preserved):
- src/ -> system/ (danos internals; the self-representation)
root.zig -> danos.zig (the kernel<->user contract module)
kernel/arch/ -> kernel/architecture/ (arch -> architecture)
device/ -> devices/ (what /system/devices reflects)
boot/ -> /boot (the loaders, top level)
- sbin/ -> split by role:
init, vfs -> system/services/<name>/<name>.zig
hpetd, busd -> system/drivers/<name>/<name>.zig
vfs-test -> system/services/vfs/vfs-test.zig (inside the vfs project)
- lib/ -> library/runtime/ (room for other libraries beside runtime)
The VFS wire protocol becomes its own module, system/services/vfs/protocol.zig
("vfs-protocol"): the vfs sub-project exposes its interface, and the runtime's
file layer imports it by name. First instance of the "protocol module" pattern
(docs/driver-model.md); usb/block will expose theirs the same way.
Also: fix a naming-standard violation in the protocol — Op -> Operation (and
req -> request, _pad -> _padding). Docs updated: /system/services added to the
FHS doc, a repository-layout section added to the docs index, and stale source
paths swept across comments and docs.
Runtime boot paths are unchanged (the bootloader still loads /sbin/init);
aligning the runtime filesystem to the FHS is a separate follow-up. Suite 35/35
plus host tests green.
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
|
|
}
|