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}