1 //! This optional feature adds support for the `log` crate, providing
2 //! a custom logger implementation which writes to a UEFI text output protocol.
3 //!
4 //! The main export of this module is the `Logger` structure,
5 //! which implements the `log` crate's trait `Log`.
6 //!
7 //! # Implementation details
8 //!
9 //! The implementation is not the most efficient, since there is no buffering done,
10 //! and the messages have to be converted from UTF-8 to UEFI's UCS-2.
11 //!
12 //! The last part also means that some Unicode characters might not be
13 //! supported by the UEFI console. Don't expect emoji output support.
14 
15 use crate::proto::console::text::Output;
16 use crate::system;
17 use core::fmt::{self, Write};
18 use core::ptr;
19 use core::sync::atomic::{AtomicPtr, Ordering};
20 
21 /// Global logger object
22 static LOGGER: Logger = Logger::new();
23 
24 /// Set up logging
25 ///
26 /// This is unsafe because you must arrange for the logger to be reset with
27 /// disable() on exit from UEFI boot services.
init()28 pub unsafe fn init() {
29     // Connect the logger to stdout.
30     system::with_stdout(|stdout| {
31         LOGGER.set_output(stdout);
32     });
33 
34     // Set the logger.
35     log::set_logger(&LOGGER).unwrap(); // Can only fail if already initialized.
36 
37     // Set logger max level to level specified by log features
38     log::set_max_level(log::STATIC_MAX_LEVEL);
39 }
40 
disable()41 pub fn disable() {
42     LOGGER.disable();
43 }
44 
45 /// Writer to the QEMU debugcon device and the debug-console of
46 /// cloud-hypervisor.
47 ///
48 /// More info: <https://phip1611.de/blog/how-to-use-qemus-debugcon-feature/>
49 #[cfg(all(
50     any(target_arch = "x86", target_arch = "x86_64"),
51     feature = "log-debugcon"
52 ))]
53 #[derive(Copy, Clone, Debug)]
54 struct DebugconWriter;
55 
56 #[cfg(all(
57     any(target_arch = "x86", target_arch = "x86_64"),
58     feature = "log-debugcon"
59 ))]
60 impl DebugconWriter {
61     const IO_PORT: u16 = 0xe9;
62 }
63 
64 #[cfg(all(
65     any(target_arch = "x86", target_arch = "x86_64"),
66     feature = "log-debugcon"
67 ))]
68 impl core::fmt::Write for DebugconWriter {
write_str(&mut self, s: &str) -> fmt::Result69     fn write_str(&mut self, s: &str) -> fmt::Result {
70         for &byte in s.as_bytes() {
71             unsafe {
72                 core::arch::asm!("outb %al, %dx", in("al") byte, in("dx") Self::IO_PORT, options(att_syntax))
73             };
74         }
75         Ok(())
76     }
77 }
78 
79 /// Logging implementation which writes to a UEFI output stream.
80 ///
81 /// If this logger is used as a global logger, you must disable it using the
82 /// `disable` method before exiting UEFI boot services in order to prevent
83 /// undefined behaviour from inadvertent logging.
84 #[derive(Debug)]
85 pub struct Logger {
86     writer: AtomicPtr<Output>,
87 }
88 
89 impl Logger {
90     /// Creates a new logger.
91     ///
92     /// The logger is initially disabled. Call [`set_output`] to enable it.
93     ///
94     /// [`set_output`]: Self::set_output
95     #[must_use]
new() -> Self96     pub const fn new() -> Self {
97         Self {
98             writer: AtomicPtr::new(ptr::null_mut()),
99         }
100     }
101 
102     /// Get the output pointer (may be null).
103     #[must_use]
output(&self) -> *mut Output104     fn output(&self) -> *mut Output {
105         self.writer.load(Ordering::Acquire)
106     }
107 
108     /// Set the [`Output`] to which the logger will write.
109     ///
110     /// If a null pointer is passed for `output`, this method is equivalent to
111     /// calling [`disable`].
112     ///
113     /// # Safety
114     ///
115     /// The `output` pointer must either be null or point to a valid [`Output`]
116     /// object. That object must remain valid until the logger is either
117     /// disabled, or `set_output` is called with a different `output`.
118     ///
119     /// You must arrange for the [`disable`] method to be called or for this
120     /// logger to be otherwise discarded before boot services are exited.
121     ///
122     /// [`disable`]: Self::disable
set_output(&self, output: *mut Output)123     pub unsafe fn set_output(&self, output: *mut Output) {
124         self.writer.store(output, Ordering::Release);
125     }
126 
127     /// Disable the logger.
disable(&self)128     pub fn disable(&self) {
129         unsafe { self.set_output(ptr::null_mut()) }
130     }
131 }
132 
133 impl log::Log for Logger {
enabled(&self, _metadata: &log::Metadata) -> bool134     fn enabled(&self, _metadata: &log::Metadata) -> bool {
135         // We decide in `log` already if something is printed. We do not
136         // need micro optimizations here.
137         true
138     }
139 
log(&self, record: &log::Record)140     fn log(&self, record: &log::Record) {
141         if let Some(writer) = unsafe { self.output().as_mut() } {
142             // Ignore all errors. Since we're in the logger implementation we
143             // can't log the error. We also don't want to panic, since logging
144             // is generally not critical functionality.
145             let _ = DecoratedLog::write(
146                 writer,
147                 record.level(),
148                 record.args(),
149                 record.file().unwrap_or("<unknown file>"),
150                 record.line().unwrap_or(0),
151             );
152         }
153 
154         #[cfg(all(
155             any(target_arch = "x86", target_arch = "x86_64"),
156             feature = "log-debugcon"
157         ))]
158         {
159             // Ignore all errors. Since we're in the logger implementation we
160             // can't log the error. We also don't want to panic, since logging
161             // is generally not critical functionality.
162             let _ = DecoratedLog::write(
163                 &mut DebugconWriter,
164                 record.level(),
165                 record.args(),
166                 record.file().unwrap_or("<unknown file>"),
167                 record.line().unwrap_or(0),
168             );
169         }
170     }
171 
flush(&self)172     fn flush(&self) {
173         // This simple logger does not buffer output.
174     }
175 }
176 
177 // The logger is not thread-safe, but the UEFI boot environment only uses one processor.
178 unsafe impl Sync for Logger {}
179 unsafe impl Send for Logger {}
180 
181 /// Writer wrapper which prints a log level in front of every line of text
182 ///
183 /// This is less easy than it sounds because...
184 ///
185 /// 1. The fmt::Arguments is a rather opaque type, the ~only thing you can do
186 ///    with it is to hand it to an fmt::Write implementation.
187 /// 2. Without using memory allocation, the easy cop-out of writing everything
188 ///    to a String then post-processing is not available.
189 ///
190 /// Therefore, we need to inject ourselves in the middle of the fmt::Write
191 /// machinery and intercept the strings that it sends to the Writer.
192 struct DecoratedLog<'writer, 'a, W: fmt::Write> {
193     writer: &'writer mut W,
194     log_level: log::Level,
195     at_line_start: bool,
196     file: &'a str,
197     line: u32,
198 }
199 
200 impl<'writer, 'a, W: fmt::Write> DecoratedLog<'writer, 'a, W> {
201     // Call this method to print a level-annotated log
write( writer: &'writer mut W, log_level: log::Level, args: &fmt::Arguments, file: &'a str, line: u32, ) -> fmt::Result202     fn write(
203         writer: &'writer mut W,
204         log_level: log::Level,
205         args: &fmt::Arguments,
206         file: &'a str,
207         line: u32,
208     ) -> fmt::Result {
209         let mut decorated_writer = Self {
210             writer,
211             log_level,
212             at_line_start: true,
213             file,
214             line,
215         };
216         writeln!(decorated_writer, "{}", *args)
217     }
218 }
219 
220 impl<'writer, 'a, W: fmt::Write> fmt::Write for DecoratedLog<'writer, 'a, W> {
write_str(&mut self, s: &str) -> fmt::Result221     fn write_str(&mut self, s: &str) -> fmt::Result {
222         // Split the input string into lines
223         let mut lines = s.lines();
224 
225         // The beginning of the input string may actually fall in the middle of
226         // a line of output. We only print the log level if it truly is at the
227         // beginning of a line of output.
228         let first = lines.next().unwrap_or("");
229         if self.at_line_start {
230             write!(
231                 self.writer,
232                 "[{:>5}]: {:>12}@{:03}: ",
233                 self.log_level, self.file, self.line
234             )?;
235             self.at_line_start = false;
236         }
237         write!(self.writer, "{first}")?;
238 
239         // For the remainder of the line iterator (if any), we know that we are
240         // truly at the beginning of lines of output.
241         for line in lines {
242             let level = self.log_level;
243             write!(self.writer, "\n{level}: {line}")?;
244         }
245 
246         // If the string ends with a newline character, we must 1/propagate it
247         // to the output (it was swallowed by the iteration) and 2/prepare to
248         // write the log level of the beginning of the next line (if any).
249         if let Some('\n') = s.chars().next_back() {
250             writeln!(self.writer)?;
251             self.at_line_start = true;
252         }
253         Ok(())
254     }
255 }
256