1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
// ---------------------------------------------------------------------------
// 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;
/// Max bytes stored per line. Wide enough that no real line truncates
/// at any plausible terminal width (a 240-col 4K framebuffer row is
/// still under this) — long lines flow in full and the terminal wraps
/// them at the screen edge instead of cutting them off.
pub const MAX_LINE: usize = 512;
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,
/// Highest timestamp seen, so the log stays monotonic even before
/// the timer is running (early-boot PIT reads are uninitialised).
last_ms: u64,
ms: [u64; MAX_LINES],
lens: [u16; MAX_LINES],
bytes: [[u8; MAX_LINE]; MAX_LINES],
}
static mut RING: Ring = Ring {
seq: 0,
dropped: 0,
count: 0,
next: 0,
last_ms: 0,
ms: [0; MAX_LINES],
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 with its timestamp in ms (line-oriented;
/// newlines are stripped). Truncated at MAX_LINE bytes. Never
/// allocates, never logs. Timestamps are clamped to be monotonic.
pub fn append(line: &str, mut ms: u64) {
let r = ring();
if ms > r.last_ms {
r.last_ms = ms;
} else {
ms = r.last_ms;
}
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.ms[r.next] = ms;
r.bytes[r.next][..n].copy_from_slice(&b[..n]);
r.lens[r.next] = n as u16;
r.next = (r.next + 1) % MAX_LINES;
r.seq += 1;
}
/// Format one serial line, write it to the serial port, and append it
/// to the ring — all under the scheduler lock, so a line from one task
/// never interleaves with another task's line (line atomicity).
/// `serial_println!` funnels through here.
pub fn emit(args: core::fmt::Arguments<'_>) {
let mut line = LineBuf::new();
let _ = core::fmt::write(&mut line, args);
let ms = crate::interrupts::uptime_ms();
crate::sched::lock();
crate::serial::write_raw(line.as_str());
crate::serial::write_raw("\r\n");
append(line.as_str(), ms);
crate::sched::unlock();
}
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("");
let s = r.ms[idx] / 1000;
let m = r.ms[idx] % 1000;
out += &alloc::format!("[{:04}.{:03}] {:06} {}\n", s, m, first + i as u64, text);
}
out
}
|