summaryrefslogtreecommitdiffstats
path: root/rsnix
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix')
-rw-r--r--rsnix/src/fs.rs8
-rw-r--r--rsnix/src/klog.rs130
-rw-r--r--rsnix/src/lib.rs1
-rw-r--r--rsnix/src/serial.rs11
4 files changed, 149 insertions, 1 deletions
diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs
index a71e51a..063560a 100644
--- a/rsnix/src/fs.rs
+++ b/rsnix/src/fs.rs
@@ -41,6 +41,7 @@ enum CharDevice {
41 Pci, 41 Pci,
42 Version, 42 Version,
43 Meminfo, 43 Meminfo,
44 Klog,
44 Null, 45 Null,
45 Zero, 46 Zero,
46 Console, 47 Console,
@@ -141,6 +142,12 @@ pub fn init() -> (usize, usize) {
141 NodeData::Char(CharDevice::Meminfo), 142 NodeData::Char(CharDevice::Meminfo),
142 ); 143 );
143 add( 144 add(
145 "klog",
146 Some(dev),
147 FileType::Char,
148 NodeData::Char(CharDevice::Klog),
149 );
150 add(
144 "null", 151 "null",
145 Some(dev), 152 Some(dev),
146 FileType::Char, 153 FileType::Char,
@@ -650,6 +657,7 @@ fn dev_text(dev: CharDevice, _pos: u64) -> String {
650 CharDevice::Pci => pci_listing(), 657 CharDevice::Pci => pci_listing(),
651 CharDevice::Version => version_text(), 658 CharDevice::Version => version_text(),
652 CharDevice::Meminfo => meminfo_text(), 659 CharDevice::Meminfo => meminfo_text(),
660 CharDevice::Klog => crate::klog::dump(),
653 CharDevice::Null | CharDevice::Console | CharDevice::Serial => String::new(), 661 CharDevice::Null | CharDevice::Console | CharDevice::Serial => String::new(),
654 CharDevice::Zero => String::new(), 662 CharDevice::Zero => String::new(),
655 } 663 }
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}
diff --git a/rsnix/src/lib.rs b/rsnix/src/lib.rs
index fc823ea..a2d41da 100644
--- a/rsnix/src/lib.rs
+++ b/rsnix/src/lib.rs
@@ -8,6 +8,7 @@ pub mod font;
8pub mod fs; 8pub mod fs;
9pub mod gop; 9pub mod gop;
10pub mod interrupts; 10pub mod interrupts;
11pub mod klog;
11pub mod memory; 12pub mod memory;
12pub mod paging; 13pub mod paging;
13pub mod pci; 14pub mod pci;
diff --git a/rsnix/src/serial.rs b/rsnix/src/serial.rs
index 03fa570..484a114 100644
--- a/rsnix/src/serial.rs
+++ b/rsnix/src/serial.rs
@@ -91,12 +91,21 @@ macro_rules! serial_print {
91 }}; 91 }};
92} 92}
93 93
94/// Prints a line to serial and appends it to the kernel log ring
95/// (klog — see /dev/klog). Every kernel-side message funnels through
96/// here, so the ring captures the whole boot sequence for free.
97/// User-facing shell output must NOT use this macro (shell output would
98/// pollute the boot log); it writes to the serial port directly.
94#[macro_export] 99#[macro_export]
95macro_rules! serial_println { 100macro_rules! serial_println {
96 () => { $crate::serial::write_str("\r\n"); }; 101 () => {
102 $crate::serial::write_str("\r\n");
103 $crate::klog::append_fmt(core::format_args!(""));
104 };
97 ($($arg:tt)*) => {{ 105 ($($arg:tt)*) => {{
98 $crate::serial_print!($($arg)*); 106 $crate::serial_print!($($arg)*);
99 $crate::serial::write_str("\r\n"); 107 $crate::serial::write_str("\r\n");
108 $crate::klog::append_fmt(core::format_args!($($arg)*));
100 }}; 109 }};
101} 110}
102 111