diff options
author | Nathan Ringo <nathan@remexre.com> | 2024-02-25 01:18:10 -0600 |
---|---|---|
committer | Nathan Ringo <nathan@remexre.com> | 2024-02-25 10:59:35 -0600 |
commit | 178cb5bb0755064a0824b1b3d564260db412ea92 (patch) | |
tree | c9917506abe9d2b9910f01a383b9320513a69c53 /kernel/src/console.rs | |
parent | c8de43bf43242c4ebac3d0ecb8e7951fe2371506 (diff) |
[console] Adds the start of a console subsystem to handle kernel logs.
The idea is that this is an output-only facility that log messages from
the log crate are written to. It has a small buffer, and relies on a log
destination regularly reading from the buffer.
In the next commit, platform-specific code will be able to provide a
"strict flush" routine, which runs after every write to the buffer and
drains the buffer. This is mainly to help kernel development, where it's
not assured that the kernel will even get as far in boot as initializing
its allocator.
Eventually, there will be a console fd that userspace can request, wait
on, and read from.
Diffstat (limited to 'kernel/src/console.rs')
-rw-r--r-- | kernel/src/console.rs | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/kernel/src/console.rs b/kernel/src/console.rs new file mode 100644 index 0000000..7364807 --- /dev/null +++ b/kernel/src/console.rs @@ -0,0 +1,179 @@ +//! The console subsystem, used for the kernel to log things. + +use core::fmt::Write; + +use crate::util::likely; +use contracts::{ensures, invariant}; +use log::Log; +use spin::Mutex; + +/// The console singleton. +static CONSOLE: Console = Console(Mutex::new(ConsoleInner::new())); + +/// Initializes the console subsystem. This must be done before any logging is +/// performed, and must +/// only be called once. +pub fn init() { + log::set_logger(&CONSOLE).expect("failed to set logger"); +} + +/// The point of interaction with the console. This is a singleton; see +/// `CONSOLE`. +struct Console(Mutex<ConsoleInner<4096>>); + +impl Log for Console { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + + let body = |line| { + let level = match record.level() { + log::Level::Error => "ERR", + log::Level::Warn => "WRN", + log::Level::Info => "INF", + log::Level::Debug => "DBG", + log::Level::Trace => "TRC", + }; + let file = record.file().unwrap_or("???"); + let args = record.args(); + + let result = if args.as_str() == Some("") { + // A silly convenience, but don't write the extra space if we're + // not going to write anything anyways. + writeln!(self.0.lock(), "[{level}][{file}:{line}]") + } else { + writeln!(self.0.lock(), "[{level}][{file}:{line}] {args}") + }; + + // Since the fmt::Write impl for ConsoleInner has a contract + // promising that it won't ever return an error, this should be + // unreachable. + result.expect("ConsoleInner failed to write"); + }; + + // Some contortions to avoid running afoul of the lifetime requirements + // of format_args... + match record.line() { + Some(line) => body(format_args!("{line}")), + None => body(format_args!("???")), + } + } + + fn flush(&self) { + todo!() + } +} + +/// The internals of the console. This is put behind a lock. +struct ConsoleInner<const BUFFER_SIZE: usize> { + /// The buffer that gets written to. Wraps on overflow. + buffer: [u8; BUFFER_SIZE], + + /// The number of bytes that have actually been written to. + len: usize, + + /// A flag for whether the log buffer has overflowed since the last time it + /// was cleared. + has_overflowed: bool, +} + +#[invariant(self.len <= BUFFER_SIZE)] +impl<const BUFFER_SIZE: usize> ConsoleInner<BUFFER_SIZE> { + const fn new() -> ConsoleInner<BUFFER_SIZE> { + ConsoleInner { + buffer: [0; BUFFER_SIZE], + len: 0, + has_overflowed: false, + } + } + + /// Writes bytes to the buffer, wrapping and setting the flag on overflow. + fn write(&mut self, bytes: &[u8]) { + // Check if there's enough room for the contents of the buffer. + let remaining = &mut self.buffer[self.len..]; + if likely(bytes.len() <= remaining.len()) { + // If there is, copy the whole buffer in. + remaining[..bytes.len()].copy_from_slice(bytes); + self.len += bytes.len(); + } else if bytes.len() >= BUFFER_SIZE { + // If not, and the bytes to write would fill up the entire buffer, + // just write over the whole thing. (This frees us from handling + // this case below.) + self.buffer + .copy_from_slice(&bytes[bytes.len() - BUFFER_SIZE..]); + self.len = BUFFER_SIZE; + self.has_overflowed = true; + } else { + // If there wasn't enough space, but there still will be enough + // space for some of the current contents, calculate how many bytes + // will be kept. + // + // This won't overflow (or even be zero), because we tested for + // that above. + let bytes_to_keep = BUFFER_SIZE - bytes.len(); + + // Copy those bytes down to the start of the buffer. + self.buffer + .copy_within(self.len - bytes_to_keep..self.len, 0); + + // Copy the new bytes to the end of the buffer. + self.buffer[bytes_to_keep..].copy_from_slice(bytes); + + // Update the length and mark the buffer as having overflowed. + self.len = BUFFER_SIZE; + self.has_overflowed = true; + } + } +} + +impl<const BUFFER_SIZE: usize> Write for ConsoleInner<BUFFER_SIZE> { + #[ensures(ret.is_ok())] + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +#[test] +fn console_handles_overflow() { + let mut console = ConsoleInner::<8>::new(); + assert_eq!(&console.buffer, b"\0\0\0\0\0\0\0\0"); + assert_eq!(console.len, 0); + assert_eq!(console.has_overflowed, false); + + console.write(b"Hello"); + assert_eq!(&console.buffer, b"Hello\0\0\0"); + assert_eq!(console.len, 5); + assert_eq!(console.has_overflowed, false); + + console.write(b", "); + assert_eq!(&console.buffer, b"Hello, \0"); + assert_eq!(console.len, 7); + assert_eq!(console.has_overflowed, false); + + console.write(b"world!"); + assert_eq!(&console.buffer, b", world!"); + assert_eq!(console.len, 8); + assert_eq!(console.has_overflowed, true); +} + +#[test] +fn console_handles_overflow_larger_than_buffer() { + let mut console = ConsoleInner::<8>::new(); + assert_eq!(&console.buffer, b"\0\0\0\0\0\0\0\0"); + assert_eq!(console.len, 0); + assert_eq!(console.has_overflowed, false); + + console.write("Hello, world!".as_bytes()); + assert_eq!(&console.buffer, b", world!"); + assert_eq!(console.len, 8); + assert_eq!(console.has_overflowed, true); +} + +/// The trait that a console backend must implement. +pub trait ConsoleBackend {} |