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