// --------------------------------------------------------------------------- // klog — kernel log ring buffer. // // A RAM ring of the last MAX_LINES serial log lines, fed by // `serial_println!` (one choke point, so every boot/low-level message is // captured for free). Exposed to the shell as the `/dev/klog` char // device, so `klog` (alias for `cat /dev/klog`) shows the boot sequence // and `klog WRITING /disk/.../boot.log` persists it via normal // redirection. // // Line-based: the ring stores whole lines up to MAX_LINE bytes; longer // lines are truncated. Each line gets a sequence number; when the ring // wraps it drops the oldest line and counts it, so a dump can report how // much history was lost. The ring never allocates (const-constructible // static), so it works from the very first log line; only `dump()` // allocates, and that is called at shell time. // --------------------------------------------------------------------------- extern crate alloc; use alloc::string::String; pub const MAX_LINES: usize = 128; pub const MAX_LINE: usize = 128; struct Ring { /// Sequence counter for the next line to be appended (total ever). seq: u64, /// Lines dropped because the ring was full (wrap overwrites). dropped: u64, /// Live lines currently in the ring (0..=MAX_LINES). count: usize, /// Slot the next line will be written to (oldest once full). next: usize, lens: [u8; MAX_LINES], bytes: [[u8; MAX_LINE]; MAX_LINES], } static mut RING: Ring = Ring { seq: 0, dropped: 0, count: 0, next: 0, lens: [0; MAX_LINES], bytes: [[0; MAX_LINE]; MAX_LINES], }; fn ring() -> &'static mut Ring { unsafe { &mut *core::ptr::addr_of_mut!(RING) } } /// Append one line to the ring (line-oriented; newlines are stripped). /// Truncated at MAX_LINE bytes. Never allocates, never logs. pub fn append(line: &str) { let r = ring(); let b = line.as_bytes(); let n = b.len().min(MAX_LINE); if r.count == MAX_LINES { r.dropped += 1; // overwrite the oldest line } else { r.count += 1; } r.bytes[r.next][..n].copy_from_slice(&b[..n]); r.lens[r.next] = n as u8; r.next = (r.next + 1) % MAX_LINES; r.seq += 1; } /// Append the formatted output of a `serial_println!` — the macro hook. /// `fmt::Write` may deliver the line in several chunks, so accumulate /// into one line buffer first, then append the whole line. pub fn append_fmt(args: core::fmt::Arguments<'_>) { let mut line = LineBuf::new(); let _ = core::fmt::write(&mut line, args); append(line.as_str()); } struct LineBuf { bytes: [u8; MAX_LINE], len: usize, } impl LineBuf { fn new() -> Self { LineBuf { bytes: [0; MAX_LINE], len: 0, } } fn as_str(&self) -> &str { core::str::from_utf8(&self.bytes[..self.len]).unwrap_or("") } } impl core::fmt::Write for LineBuf { fn write_str(&mut self, s: &str) -> core::fmt::Result { let n = s.len().min(MAX_LINE - self.len); self.bytes[self.len..self.len + n].copy_from_slice(&s.as_bytes()[..n]); self.len += n; Ok(()) } } /// The whole log as text: a header with the wrap/drop accounting, then /// one line per entry, each prefixed with its sequence number. pub fn dump() -> String { let r = ring(); let mut out = String::new(); if r.count == 0 { out += "klog: no messages logged yet\n"; return out; } let first = r.seq - r.count as u64; let last = r.seq - 1; out += &alloc::format!( "klog: {} line(s) kept, {} dropped on wrap, seq {}..{}\n", r.count, r.dropped, first, last ); let start = (r.next + MAX_LINES - r.count) % MAX_LINES; for i in 0..r.count { let idx = (start + i) % MAX_LINES; let n = r.lens[idx] as usize; let text = core::str::from_utf8(&r.bytes[idx][..n]).unwrap_or(""); out += &alloc::format!("{:06} {}\n", first + i as u64, text); } out }