summaryrefslogtreecommitdiffstats
path: root/rsnix/src/klog.rs
blob: 1e234602463e15691d470402a8986e8c5a3a2ed7 (plain) (blame)
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
// ---------------------------------------------------------------------------
// 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
}