⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/
log_service.rs

1//! Structured kernel log service
2//!
3//! Provides a fixed-size, heap-free circular buffer of structured log entries.
4//! Each entry carries a timestamp, severity level, subsystem tag, and a
5//! fixed-length message. The service is stored as global state behind a
6//! [`spin::Mutex`] and accessed through a small public API.
7//!
8//! # Usage
9//!
10//! ```ignore
11//! log_service::log_init();
12//! log_service::klog(LogLevel::Info, "sched", "scheduler initialized");
13//! let n = log_service::log_count();
14//! ```
15//!
16//! The buffer holds up to [`LOG_BUFFER_CAPACITY`] entries. Once full it wraps
17//! around and silently overwrites the oldest entries.
18
19// Log service module
20
21use spin::Mutex;
22
23use crate::sync::once_lock::GlobalState;
24
25// ---------------------------------------------------------------------------
26// Configuration
27// ---------------------------------------------------------------------------
28
29/// Maximum number of log entries the circular buffer can hold.
30const LOG_BUFFER_CAPACITY: usize = 256;
31
32/// Maximum length (in bytes) of a log message stored in a [`LogEntry`].
33const LOG_MESSAGE_MAX_LEN: usize = 128;
34
35/// Maximum length (in bytes) of the subsystem tag in a [`LogEntry`].
36const LOG_SUBSYSTEM_MAX_LEN: usize = 16;
37
38// ---------------------------------------------------------------------------
39// Types
40// ---------------------------------------------------------------------------
41
42/// Severity levels for kernel log messages.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
44#[repr(u8)]
45pub enum LogLevel {
46    /// Unrecoverable or critical errors.
47    Error = 0,
48    /// Conditions that may indicate a problem.
49    Warn = 1,
50    /// Normal operational messages.
51    Info = 2,
52    /// Verbose diagnostic output.
53    Debug = 3,
54    /// Very detailed tracing information.
55    Trace = 4,
56}
57
58/// A single structured log entry.
59///
60/// All fields are stored inline with fixed-size arrays so that the entry
61/// can live in a static circular buffer without heap allocation.
62#[derive(Clone)]
63pub struct LogEntry {
64    /// Milliseconds since boot (via `arch::timer::get_timestamp_ms`).
65    pub timestamp_ms: u64,
66    /// Severity of the message.
67    pub level: LogLevel,
68    /// Short subsystem identifier (e.g. `"sched"`, `"mm"`, `"ipc"`).
69    /// Stored as a fixed-size byte array with the actual length tracked
70    /// separately.
71    subsystem_buf: [u8; LOG_SUBSYSTEM_MAX_LEN],
72    subsystem_len: u8,
73    /// The log message text, truncated to [`LOG_MESSAGE_MAX_LEN`] bytes.
74    message_buf: [u8; LOG_MESSAGE_MAX_LEN],
75    message_len: u8,
76}
77
78impl LogEntry {
79    /// Create a zeroed, empty entry (used to initialize the buffer).
80    const fn empty() -> Self {
81        Self {
82            timestamp_ms: 0,
83            level: LogLevel::Trace,
84            subsystem_buf: [0u8; LOG_SUBSYSTEM_MAX_LEN],
85            subsystem_len: 0,
86            message_buf: [0u8; LOG_MESSAGE_MAX_LEN],
87            message_len: 0,
88        }
89    }
90
91    /// Return the subsystem tag as a `&str`.
92    pub fn subsystem(&self) -> &str {
93        let len = self.subsystem_len as usize;
94        // SAFETY/invariant: subsystem_len is always set from a valid UTF-8
95        // source (an incoming &str) and capped at LOG_SUBSYSTEM_MAX_LEN.
96        core::str::from_utf8(&self.subsystem_buf[..len]).unwrap_or("")
97    }
98
99    /// Return the message text as a `&str`.
100    pub fn message(&self) -> &str {
101        let len = self.message_len as usize;
102        core::str::from_utf8(&self.message_buf[..len]).unwrap_or("")
103    }
104}
105
106// ---------------------------------------------------------------------------
107// Circular buffer
108// ---------------------------------------------------------------------------
109
110/// Fixed-size circular buffer of [`LogEntry`] items.
111///
112/// Uses head/tail indices with a count to distinguish empty from full.
113struct LogBuffer {
114    entries: [LogEntry; LOG_BUFFER_CAPACITY],
115    /// Index of the next slot to write.
116    head: usize,
117    /// Total number of valid entries (capped at `LOG_BUFFER_CAPACITY`).
118    count: usize,
119}
120
121impl LogBuffer {
122    /// Create a new empty buffer.
123    fn new() -> Self {
124        // Initialize with empty entries using array::from_fn to avoid Copy
125        // requirement (LogEntry is Clone but not Copy due to large arrays).
126        const EMPTY: LogEntry = LogEntry::empty();
127        Self {
128            entries: [EMPTY; LOG_BUFFER_CAPACITY],
129            head: 0,
130            count: 0,
131        }
132    }
133
134    /// Append a log entry, overwriting the oldest if full.
135    fn push(&mut self, entry: LogEntry) {
136        self.entries[self.head] = entry;
137        self.head = (self.head + 1) % LOG_BUFFER_CAPACITY;
138        if self.count < LOG_BUFFER_CAPACITY {
139            self.count += 1;
140        }
141    }
142
143    /// Number of entries currently stored.
144    fn len(&self) -> usize {
145        self.count
146    }
147
148    /// Clear all entries.
149    fn clear(&mut self) {
150        self.head = 0;
151        self.count = 0;
152    }
153
154    /// Return the tail index (oldest entry).
155    fn tail(&self) -> usize {
156        if self.count < LOG_BUFFER_CAPACITY {
157            0
158        } else {
159            self.head // when full, head == tail (oldest)
160        }
161    }
162
163    /// Get the entry at logical index `i` (0 = oldest).
164    ///
165    /// Returns `None` if `i >= count`.
166    fn get(&self, i: usize) -> Option<&LogEntry> {
167        if i >= self.count {
168            return None;
169        }
170        let physical = (self.tail() + i) % LOG_BUFFER_CAPACITY;
171        Some(&self.entries[physical])
172    }
173}
174
175// ---------------------------------------------------------------------------
176// LogService
177// ---------------------------------------------------------------------------
178
179/// The kernel log service wrapping a [`LogBuffer`].
180struct LogService {
181    buffer: LogBuffer,
182}
183
184impl LogService {
185    fn new() -> Self {
186        Self {
187            buffer: LogBuffer::new(),
188        }
189    }
190
191    /// Record a log entry.
192    fn log(&mut self, level: LogLevel, subsystem: &str, message: &str) {
193        let timestamp_ms = crate::arch::timer::get_timestamp_ms();
194
195        let mut subsystem_buf = [0u8; LOG_SUBSYSTEM_MAX_LEN];
196        let sub_len = subsystem.len().min(LOG_SUBSYSTEM_MAX_LEN);
197        subsystem_buf[..sub_len].copy_from_slice(&subsystem.as_bytes()[..sub_len]);
198
199        let mut message_buf = [0u8; LOG_MESSAGE_MAX_LEN];
200        let msg_len = message.len().min(LOG_MESSAGE_MAX_LEN);
201        message_buf[..msg_len].copy_from_slice(&message.as_bytes()[..msg_len]);
202
203        let entry = LogEntry {
204            timestamp_ms,
205            level,
206            subsystem_buf,
207            subsystem_len: sub_len as u8,
208            message_buf,
209            message_len: msg_len as u8,
210        };
211
212        self.buffer.push(entry);
213    }
214
215    /// Number of entries in the buffer.
216    fn count(&self) -> usize {
217        self.buffer.len()
218    }
219
220    /// Clear all entries.
221    fn clear(&mut self) {
222        self.buffer.clear();
223    }
224}
225
226// ---------------------------------------------------------------------------
227// Global state
228// ---------------------------------------------------------------------------
229
230static LOG_SERVICE: GlobalState<Mutex<LogService>> = GlobalState::new();
231
232// ---------------------------------------------------------------------------
233// Public API
234// ---------------------------------------------------------------------------
235
236/// Initialize the kernel log service.
237///
238/// Must be called once during kernel boot, after the timer subsystem is
239/// available. Subsequent calls are silently ignored (returns `Ok(())`).
240pub fn log_init() {
241    let _ = LOG_SERVICE.init(Mutex::new(LogService::new()));
242}
243
244/// Record a structured log entry.
245///
246/// If the log service has not been initialized yet (i.e., called before
247/// `log_init()`), the message is silently dropped.
248pub fn klog(level: LogLevel, subsystem: &str, message: &str) {
249    LOG_SERVICE.with_mut(|lock| {
250        lock.lock().log(level, subsystem, message);
251    });
252}
253
254/// Iterate over all buffered log entries from oldest to newest, calling `f`
255/// for each.
256///
257/// Returns the number of entries visited, or `None` if the service is not
258/// initialized.
259pub fn log_drain<F: FnMut(&LogEntry)>(mut f: F) -> Option<usize> {
260    LOG_SERVICE.with(|lock| {
261        let service = lock.lock();
262        let n = service.buffer.len();
263        for i in 0..n {
264            if let Some(entry) = service.buffer.get(i) {
265                f(entry);
266            }
267        }
268        n
269    })
270}
271
272/// Return the number of entries currently in the log buffer.
273///
274/// Returns `None` if the service is not initialized.
275pub fn log_count() -> Option<usize> {
276    LOG_SERVICE.with(|lock| lock.lock().count())
277}
278
279/// Clear all log entries.
280///
281/// Returns `None` if the service is not initialized.
282pub fn log_clear() -> Option<()> {
283    LOG_SERVICE.with_mut(|lock| lock.lock().clear())
284}