From 358238c2fd5a48a6f3eb9bcd3caee2aea157cd08 Mon Sep 17 00:00:00 2001 From: Kevin Boos <1139460+kevinaboos@users.noreply.github.com> Date: Thu, 19 Aug 2021 12:18:26 -0700 Subject: [PATCH] Generalize the `logger` to use any backend that implements `fmt::Write`. (#434) * Removes the hard dependency on `serial_port` from `logger`, which causes some problematic dependency chains. --- Cargo.lock | 1 - kernel/io/src/lib.rs | 8 ++++ kernel/logger/Cargo.toml | 3 -- kernel/logger/src/lib.rs | 83 +++++++++++++++++++++---------------- kernel/nano_core/src/lib.rs | 4 +- 5 files changed, 58 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14c0c56833..15037fb444 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1398,7 +1398,6 @@ version = "0.1.0" dependencies = [ "irq_safety", "log", - "serial_port", "spin 0.9.0", ] diff --git a/kernel/io/src/lib.rs b/kernel/io/src/lib.rs index 822cfc1d56..0cdfa5fb42 100644 --- a/kernel/io/src/lib.rs +++ b/kernel/io/src/lib.rs @@ -643,6 +643,7 @@ impl Seek for Writer where IO: KnownLength { /// * [`BlockReader`] and [`BlockWriter`] /// * [`ByteReader`] and [`ByteWriter`] /// * [`bare_io::Read`], [`bare_io::Write`], and [`bare_io::Seek`] +/// * [`core::fmt::Write`] /// /// # Usage and Examples /// The Rust compiler has difficulty inferring all of the types needed in this struct; @@ -770,6 +771,13 @@ impl<'io, IO, L, B> bare_io::Seek for LockableIo<'io, IO, L, B> { delegate!{ to self.lock_mut() { fn seek(&mut self, position: bare_io::SeekFrom) -> bare_io::Result; } } } +impl<'io, IO, L, B> core::fmt::Write for LockableIo<'io, IO, L, B> + where IO: core::fmt::Write + 'io + ?Sized, L: for <'a> Lockable<'a, IO> + ?Sized, B: Borrow, +{ + delegate!{ to self.lock_mut() { + fn write_str(&mut self, s: &str) -> core::fmt::Result; + } } +} /// Calculates block-wise bounds for an I/O transfer diff --git a/kernel/logger/Cargo.toml b/kernel/logger/Cargo.toml index 0c234000be..99686f4af6 100644 --- a/kernel/logger/Cargo.toml +++ b/kernel/logger/Cargo.toml @@ -8,9 +8,6 @@ build = "../../build.rs" spin = "0.9.0" log = "0.4.8" -[dependencies.serial_port] -path = "../serial_port" - [dependencies.irq_safety] git = "https://github.com/theseus-os/irq_safety" diff --git a/kernel/logger/src/lib.rs b/kernel/logger/src/lib.rs index eb121cb2eb..5115cbf511 100644 --- a/kernel/logger/src/lib.rs +++ b/kernel/logger/src/lib.rs @@ -1,11 +1,13 @@ //! A basic logger implementation for system-wide logging in Theseus. //! -//! This enables Theseus crates to use the `log` crate's macros anywhere. -//! Currently, log statements are written to one or more serial ports. +//! This enables Theseus crates to use the `log` crate's macros anywhere, +//! such as `error!()`, `warn!()`, `info!()`, `debug!()`, and `trace!()`. +//! +//! Currently, log statements are written to one or more **writers`, +//! which are objects that implement the [`core::fmt::Write`] trait. #![no_std] -extern crate serial_port; extern crate log; extern crate spin; extern crate irq_safety; @@ -13,23 +15,28 @@ extern crate irq_safety; use log::{Record, Level, SetLoggerError, Metadata, Log}; use core::fmt::{self, Write}; use spin::Once; -use serial_port::{SerialPort, SerialPortAddress}; use irq_safety::MutexIrqSafe; - -/// The static logger instance. -/// This is "static" only because it's required by the `log` crate's design. -static LOGGER: Once = Once::new(); +/// The singleton system-wide logger instance. +/// +/// This is "static" only because it's required by the `log` crate. +static LOGGER: Once> = Once::new(); /// By default, Theseus will print all log levels, including `Trace` and above. -const DEFAULT_LOG_LEVEL: Level = Level::Trace; +pub const DEFAULT_LOG_LEVEL: Level = Level::Trace; +/// The maximum number of writers: backends to which log streams can be outputted. +pub const LOG_MAX_WRITERS: usize = 2; + +/// The signature of a callback function that will optionally be invoked +/// on every log statement to be printed, which enables log mirroring. +/// See [`mirror_to_vga()`]. pub type LogOutputFunc = fn(fmt::Arguments); static MIRROR_VGA_FUNC: Once = Once::new(); -/// See ANSI terminal formatting schemes +/// ANSI style codes for basic colors. #[allow(dead_code)] -pub enum LogColor { +enum LogColor { Black, Red, Green, @@ -42,7 +49,7 @@ pub enum LogColor { } impl LogColor { - pub fn as_terminal_string(&self) -> &'static str { + fn as_terminal_string(&self) -> &'static str { match *self { // \x1b is the ESC character (0x1B) LogColor::Black => "\x1b[30m", @@ -66,25 +73,31 @@ pub fn mirror_to_vga(func: LogOutputFunc) { /// A struct that holds information about logging destinations in Theseus. /// /// This is the "backend" for the `log` crate that allows Theseus to use its `log!()` macros. -/// Currently, it supports emitting log messages to up to 4 serial ports. -#[derive(Default)] -struct Logger { - serial_ports: [Option<&'static MutexIrqSafe>; 4], +/// +/// We force the use of static references here to enable this crate to be used +/// before dynamic heap allocation has been set up. +struct Logger { + writers: [Option<&'static MutexIrqSafe>; N], +} +impl Default for Logger<{N}> { + fn default() -> Self { + Logger { writers: [None; N] } + } } -impl Logger { +impl Logger<{N}> { /// Re-implementation of the function from `fmt::Write`, but it doesn't require `&mut self`. fn write_fmt(&self, arguments: fmt::Arguments) -> fmt::Result { - for serial_port in self.serial_ports.iter().flatten() { - let _result = serial_port.lock().write_fmt(arguments); + for writer in self.writers.iter().flatten() { + let _result = writer.lock().write_fmt(arguments); // If there was an error above, there's literally nothing we can do but ignore it, - // because there is no other lower-level way to log errors than the serial port. + // because there is no other lower-level way to log errors than this logger. } Ok(()) } } -impl Log for Logger { +impl Log for Logger<{N}> { #[inline(always)] fn enabled(&self, metadata: &Metadata) -> bool { metadata.level() <= log::max_level() @@ -130,7 +143,7 @@ impl Log for Logger { } fn flush(&self) { - // flushing the log is a no-op, since there is no write buffering yet + // flushing the log is a no-op, since there is no write buffering. } } @@ -139,24 +152,24 @@ impl Log for Logger { /// /// # Arguments /// * `log_level`: the log level that should be used. -/// If `None`, the `DEFAULT_LOG_LEVEL` will be used. -/// * `serial_ports`: an iterator over the serial ports that the system logger +/// If `None`, the [`DEFAULT_LOG_LEVEL`] will be used. +/// * `writers`: an iterator over the backends that the system logger /// will write log messages to. -/// Typically this is just a single port, e.g., `&[COM1]`. +/// Typically this is just a single writer, such as the COM1 serial port. /// -/// This function will initialize up to a maximum of 4 serial ports and use them for logging. -/// Serial ports after the first 4 in the `serial_ports` argument will be ignored. -/// -/// This function also initializes and takes ownership of all specified serial ports -/// such that it can atomically write log messages to them. -pub fn init<'p>( +/// This function will initialize the logger with a maximum of [`LOG_MAX_WRITERS`] writers; +/// any additional writers in the given `writers` iterator will be ignored. +/// +/// This function accepts only static references to log writers in order to +/// enable loggers to be used before dynamic heap allocation has been set up. +pub fn init<'i, W: Write + Send + 'static>( log_level: Option, - serial_ports: impl IntoIterator + writers: impl IntoIterator>, ) -> Result<(), SetLoggerError> { let mut logger = Logger::default(); - for (base_port, logger_serial_port) in serial_ports.into_iter().take(4).zip(&mut logger.serial_ports) { - *logger_serial_port = Some(serial_port::get_serial_port(*base_port)); - } + for (writer, logger_writer) in writers.into_iter().take(LOG_MAX_WRITERS).zip(&mut logger.writers) { + *logger_writer = Some(*writer); + } let static_logger = LOGGER.call_once(|| logger); log::set_logger(static_logger)?; diff --git a/kernel/nano_core/src/lib.rs b/kernel/nano_core/src/lib.rs index aaefbedd64..ab19f6dac5 100644 --- a/kernel/nano_core/src/lib.rs +++ b/kernel/nano_core/src/lib.rs @@ -101,8 +101,8 @@ pub extern "C" fn nano_core_start( println_raw!("Entered nano_core_start(). Interrupts disabled."); // Initialize the logger up front so we can see early log messages for debugging. - let logger_serial_ports = [serial_port::SerialPortAddress::COM1]; // some servers use COM2 instead. - try_exit!(logger::init(None, &logger_serial_ports).map_err(|_a| "couldn't init logger!")); + let logger_writers = [serial_port::get_serial_port(serial_port::SerialPortAddress::COM1)]; // some servers use COM2 instead. + try_exit!(logger::init(None, &logger_writers).map_err(|_a| "couldn't init logger!")); info!("Logger initialized."); println_raw!("nano_core_start(): initialized logger.");