| //! This optional feature adds support for the `log` crate, providing |
| //! a custom logger implementation which writes to a UEFI text output protocol. |
| //! |
| //! The main export of this module is the `Logger` structure, |
| //! which implements the `log` crate's trait `Log`. |
| //! |
| //! # Implementation details |
| //! |
| //! The implementation is not the most efficient, since there is no buffering done, |
| //! and the messages have to be converted from UTF-8 to UEFI's UCS-2. |
| //! |
| //! The last part also means that some Unicode characters might not be |
| //! supported by the UEFI console. Don't expect emoji output support. |
| |
| use crate::proto::console::text::Output; |
| use crate::system; |
| use core::fmt::{self, Write}; |
| use core::ptr; |
| use core::sync::atomic::{AtomicPtr, Ordering}; |
| |
| /// Global logger object |
| static LOGGER: Logger = Logger::new(); |
| |
| /// Set up logging |
| /// |
| /// This is unsafe because you must arrange for the logger to be reset with |
| /// disable() on exit from UEFI boot services. |
| pub unsafe fn init() { |
| // Connect the logger to stdout. |
| system::with_stdout(|stdout| { |
| LOGGER.set_output(stdout); |
| }); |
| |
| // Set the logger. |
| log::set_logger(&LOGGER).unwrap(); // Can only fail if already initialized. |
| |
| // Set logger max level to level specified by log features |
| log::set_max_level(log::STATIC_MAX_LEVEL); |
| } |
| |
| pub fn disable() { |
| LOGGER.disable(); |
| } |
| |
| /// Writer to the QEMU debugcon device and the debug-console of |
| /// cloud-hypervisor. |
| /// |
| /// More info: <https://phip1611.de/blog/how-to-use-qemus-debugcon-feature/> |
| #[cfg(all( |
| any(target_arch = "x86", target_arch = "x86_64"), |
| feature = "log-debugcon" |
| ))] |
| #[derive(Copy, Clone, Debug)] |
| struct DebugconWriter; |
| |
| #[cfg(all( |
| any(target_arch = "x86", target_arch = "x86_64"), |
| feature = "log-debugcon" |
| ))] |
| impl DebugconWriter { |
| const IO_PORT: u16 = 0xe9; |
| } |
| |
| #[cfg(all( |
| any(target_arch = "x86", target_arch = "x86_64"), |
| feature = "log-debugcon" |
| ))] |
| impl core::fmt::Write for DebugconWriter { |
| fn write_str(&mut self, s: &str) -> fmt::Result { |
| for &byte in s.as_bytes() { |
| unsafe { |
| core::arch::asm!("outb %al, %dx", in("al") byte, in("dx") Self::IO_PORT, options(att_syntax)) |
| }; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// Logging implementation which writes to a UEFI output stream. |
| /// |
| /// If this logger is used as a global logger, you must disable it using the |
| /// `disable` method before exiting UEFI boot services in order to prevent |
| /// undefined behaviour from inadvertent logging. |
| #[derive(Debug)] |
| pub struct Logger { |
| writer: AtomicPtr<Output>, |
| } |
| |
| impl Logger { |
| /// Creates a new logger. |
| /// |
| /// The logger is initially disabled. Call [`set_output`] to enable it. |
| /// |
| /// [`set_output`]: Self::set_output |
| #[must_use] |
| pub const fn new() -> Self { |
| Self { |
| writer: AtomicPtr::new(ptr::null_mut()), |
| } |
| } |
| |
| /// Get the output pointer (may be null). |
| #[must_use] |
| fn output(&self) -> *mut Output { |
| self.writer.load(Ordering::Acquire) |
| } |
| |
| /// Set the [`Output`] to which the logger will write. |
| /// |
| /// If a null pointer is passed for `output`, this method is equivalent to |
| /// calling [`disable`]. |
| /// |
| /// # Safety |
| /// |
| /// The `output` pointer must either be null or point to a valid [`Output`] |
| /// object. That object must remain valid until the logger is either |
| /// disabled, or `set_output` is called with a different `output`. |
| /// |
| /// You must arrange for the [`disable`] method to be called or for this |
| /// logger to be otherwise discarded before boot services are exited. |
| /// |
| /// [`disable`]: Self::disable |
| pub unsafe fn set_output(&self, output: *mut Output) { |
| self.writer.store(output, Ordering::Release); |
| } |
| |
| /// Disable the logger. |
| pub fn disable(&self) { |
| unsafe { self.set_output(ptr::null_mut()) } |
| } |
| } |
| |
| impl log::Log for Logger { |
| fn enabled(&self, _metadata: &log::Metadata) -> bool { |
| // We decide in `log` already if something is printed. We do not |
| // need micro optimizations here. |
| true |
| } |
| |
| fn log(&self, record: &log::Record) { |
| if let Some(writer) = unsafe { self.output().as_mut() } { |
| // Ignore all errors. Since we're in the logger implementation we |
| // can't log the error. We also don't want to panic, since logging |
| // is generally not critical functionality. |
| let _ = DecoratedLog::write( |
| writer, |
| record.level(), |
| record.args(), |
| record.file().unwrap_or("<unknown file>"), |
| record.line().unwrap_or(0), |
| ); |
| } |
| |
| #[cfg(all( |
| any(target_arch = "x86", target_arch = "x86_64"), |
| feature = "log-debugcon" |
| ))] |
| { |
| // Ignore all errors. Since we're in the logger implementation we |
| // can't log the error. We also don't want to panic, since logging |
| // is generally not critical functionality. |
| let _ = DecoratedLog::write( |
| &mut DebugconWriter, |
| record.level(), |
| record.args(), |
| record.file().unwrap_or("<unknown file>"), |
| record.line().unwrap_or(0), |
| ); |
| } |
| } |
| |
| fn flush(&self) { |
| // This simple logger does not buffer output. |
| } |
| } |
| |
| // The logger is not thread-safe, but the UEFI boot environment only uses one processor. |
| unsafe impl Sync for Logger {} |
| unsafe impl Send for Logger {} |
| |
| /// Writer wrapper which prints a log level in front of every line of text |
| /// |
| /// This is less easy than it sounds because... |
| /// |
| /// 1. The fmt::Arguments is a rather opaque type, the ~only thing you can do |
| /// with it is to hand it to an fmt::Write implementation. |
| /// 2. Without using memory allocation, the easy cop-out of writing everything |
| /// to a String then post-processing is not available. |
| /// |
| /// Therefore, we need to inject ourselves in the middle of the fmt::Write |
| /// machinery and intercept the strings that it sends to the Writer. |
| struct DecoratedLog<'writer, 'a, W: fmt::Write> { |
| writer: &'writer mut W, |
| log_level: log::Level, |
| at_line_start: bool, |
| file: &'a str, |
| line: u32, |
| } |
| |
| impl<'writer, 'a, W: fmt::Write> DecoratedLog<'writer, 'a, W> { |
| // Call this method to print a level-annotated log |
| fn write( |
| writer: &'writer mut W, |
| log_level: log::Level, |
| args: &fmt::Arguments, |
| file: &'a str, |
| line: u32, |
| ) -> fmt::Result { |
| let mut decorated_writer = Self { |
| writer, |
| log_level, |
| at_line_start: true, |
| file, |
| line, |
| }; |
| writeln!(decorated_writer, "{}", *args) |
| } |
| } |
| |
| impl<'writer, 'a, W: fmt::Write> fmt::Write for DecoratedLog<'writer, 'a, W> { |
| fn write_str(&mut self, s: &str) -> fmt::Result { |
| // Split the input string into lines |
| let mut lines = s.lines(); |
| |
| // The beginning of the input string may actually fall in the middle of |
| // a line of output. We only print the log level if it truly is at the |
| // beginning of a line of output. |
| let first = lines.next().unwrap_or(""); |
| if self.at_line_start { |
| write!( |
| self.writer, |
| "[{:>5}]: {:>12}@{:03}: ", |
| self.log_level, self.file, self.line |
| )?; |
| self.at_line_start = false; |
| } |
| write!(self.writer, "{first}")?; |
| |
| // For the remainder of the line iterator (if any), we know that we are |
| // truly at the beginning of lines of output. |
| for line in lines { |
| let level = self.log_level; |
| write!(self.writer, "\n{level}: {line}")?; |
| } |
| |
| // If the string ends with a newline character, we must 1/propagate it |
| // to the output (it was swallowed by the iteration) and 2/prepare to |
| // write the log level of the beginning of the next line (if any). |
| if let Some('\n') = s.chars().next_back() { |
| writeln!(self.writer)?; |
| self.at_line_start = true; |
| } |
| Ok(()) |
| } |
| } |