summaryrefslogtreecommitdiffstats
path: root/rsnix/src/klog.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/klog.rs130
1 files changed, 130 insertions, 0 deletions
diff --git a/rsnix/src/klog.rs b/rsnix/src/klog.rs
new file mode 100644
index 0000000..1e23460
--- /dev/null
+++ b/rsnix/src/klog.rs
@@ -0,0 +1,130 @@
1// ---------------------------------------------------------------------------
2// klog — kernel log ring buffer.
3//
4// A RAM ring of the last MAX_LINES serial log lines, fed by
5// `serial_println!` (one choke point, so every boot/low-level message is
6// captured for free). Exposed to the shell as the `/dev/klog` char
7// device, so `klog` (alias for `cat /dev/klog`) shows the boot sequence
8// and `klog WRITING /disk/.../boot.log` persists it via normal
9// redirection.
10//
11// Line-based: the ring stores whole lines up to MAX_LINE bytes; longer
12// lines are truncated. Each line gets a sequence number; when the ring
13// wraps it drops the oldest line and counts it, so a dump can report how
14// much history was lost. The ring never allocates (const-constructible
15// static), so it works from the very first log line; only `dump()`
16// allocates, and that is called at shell time.
17// ---------------------------------------------------------------------------
18
19extern crate alloc;
20
21use alloc::string::String;
22
23pub const MAX_LINES: usize = 128;
24pub const MAX_LINE: usize = 128;
25
26struct Ring {
27 /// Sequence counter for the next line to be appended (total ever).
28 seq: u64,
29 /// Lines dropped because the ring was full (wrap overwrites).
30 dropped: u64,
31 /// Live lines currently in the ring (0..=MAX_LINES).
32 count: usize,
33 /// Slot the next line will be written to (oldest once full).
34 next: usize,
35 lens: [u8; MAX_LINES],
36 bytes: [[u8; MAX_LINE]; MAX_LINES],
37}
38
39static mut RING: Ring = Ring {
40 seq: 0,
41 dropped: 0,
42 count: 0,
43 next: 0,
44 lens: [0; MAX_LINES],
45 bytes: [[0; MAX_LINE]; MAX_LINES],
46};
47
48fn ring() -> &'static mut Ring {
49 unsafe { &mut *core::ptr::addr_of_mut!(RING) }
50}
51
52/// Append one line to the ring (line-oriented; newlines are stripped).
53/// Truncated at MAX_LINE bytes. Never allocates, never logs.
54pub fn append(line: &str) {
55 let r = ring();
56 let b = line.as_bytes();
57 let n = b.len().min(MAX_LINE);
58 if r.count == MAX_LINES {
59 r.dropped += 1; // overwrite the oldest line
60 } else {
61 r.count += 1;
62 }
63 r.bytes[r.next][..n].copy_from_slice(&b[..n]);
64 r.lens[r.next] = n as u8;
65 r.next = (r.next + 1) % MAX_LINES;
66 r.seq += 1;
67}
68
69/// Append the formatted output of a `serial_println!` — the macro hook.
70/// `fmt::Write` may deliver the line in several chunks, so accumulate
71/// into one line buffer first, then append the whole line.
72pub fn append_fmt(args: core::fmt::Arguments<'_>) {
73 let mut line = LineBuf::new();
74 let _ = core::fmt::write(&mut line, args);
75 append(line.as_str());
76}
77
78struct LineBuf {
79 bytes: [u8; MAX_LINE],
80 len: usize,
81}
82
83impl LineBuf {
84 fn new() -> Self {
85 LineBuf {
86 bytes: [0; MAX_LINE],
87 len: 0,
88 }
89 }
90 fn as_str(&self) -> &str {
91 core::str::from_utf8(&self.bytes[..self.len]).unwrap_or("")
92 }
93}
94
95impl core::fmt::Write for LineBuf {
96 fn write_str(&mut self, s: &str) -> core::fmt::Result {
97 let n = s.len().min(MAX_LINE - self.len);
98 self.bytes[self.len..self.len + n].copy_from_slice(&s.as_bytes()[..n]);
99 self.len += n;
100 Ok(())
101 }
102}
103
104/// The whole log as text: a header with the wrap/drop accounting, then
105/// one line per entry, each prefixed with its sequence number.
106pub fn dump() -> String {
107 let r = ring();
108 let mut out = String::new();
109 if r.count == 0 {
110 out += "klog: no messages logged yet\n";
111 return out;
112 }
113 let first = r.seq - r.count as u64;
114 let last = r.seq - 1;
115 out += &alloc::format!(
116 "klog: {} line(s) kept, {} dropped on wrap, seq {}..{}\n",
117 r.count,
118 r.dropped,
119 first,
120 last
121 );
122 let start = (r.next + MAX_LINES - r.count) % MAX_LINES;
123 for i in 0..r.count {
124 let idx = (start + i) % MAX_LINES;
125 let n = r.lens[idx] as usize;
126 let text = core::str::from_utf8(&r.bytes[idx][..n]).unwrap_or("");
127 out += &alloc::format!("{:06} {}\n", first + i as u64, text);
128 }
129 out
130}