diff options
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 {} |