Skip to main content

openhcl_boot/
boot_logger.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Logging support for the bootshim.
5//!
6//! The bootshim performs no filtering of its logging messages when running in
7//! a confidential VM. This is because it runs before any keys can be accessed
8//! or any guest code is executed, and therefore it can not leak anything
9//! sensitive.
10
11#[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
12use crate::arch::snp::SnpSerialBackend;
13#[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
14use crate::arch::tdx::TdxSerialBackend;
15use crate::host_params::shim_params::IsolationType;
16use crate::single_threaded::SingleThreaded;
17use core::cell::RefCell;
18use core::fmt;
19use core::fmt::Write;
20use host_fdt_parser::ComInfo;
21use memory_range::MemoryRange;
22#[cfg(target_arch = "aarch64")]
23use minimal_rt::arch::Serial;
24use string_page_buf::StringBuffer;
25#[cfg(target_arch = "x86_64")]
26use uart_16550::BaudRate;
27#[cfg(target_arch = "x86_64")]
28use uart_16550::Config;
29#[cfg(target_arch = "x86_64")]
30use uart_16550::Uart16550;
31#[cfg(target_arch = "x86_64")]
32use uart_16550::Uart16550Tty;
33#[cfg(target_arch = "x86_64")]
34use uart_16550::backend::Backend;
35#[cfg(target_arch = "x86_64")]
36use uart_16550::backend::PioBackend;
37
38enum Logger {
39    #[cfg(target_arch = "x86_64")]
40    Serial(Uart16550Tty<PioBackend>),
41    #[cfg(target_arch = "aarch64")]
42    #[expect(dead_code)]
43    Serial(Serial),
44    #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
45    TdxSerial(Uart16550Tty<TdxSerialBackend>),
46    #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
47    SnpSerial(Uart16550Tty<SnpSerialBackend>),
48    None,
49}
50
51/// Initializes the serial device at the speed advertised by the host, which
52/// is also passed to Linux. Returns `None` if the device isn't present or the
53/// speed is invalid.
54#[cfg(target_arch = "x86_64")]
55fn init_serial<B: Backend>(mut uart: Uart16550<B>, baud_rate: u32) -> Option<Uart16550Tty<B>> {
56    // The host controls the speed, and uart_16550 debug-asserts that it is
57    // non-zero.
58    if baud_rate == 0 {
59        return None;
60    }
61    let config = Config {
62        baud_rate: BaudRate::Custom(baud_rate),
63        ..Config::DEFAULT
64    };
65    uart.init(config).ok()?;
66    Some(Uart16550Tty::from_inner(uart))
67}
68
69impl Logger {
70    fn write_str(&mut self, s: &str) -> fmt::Result {
71        match self {
72            Logger::Serial(serial) => serial.write_str(s),
73            #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
74            Logger::TdxSerial(serial) => serial.write_str(s),
75            #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
76            Logger::SnpSerial(serial) => serial.write_str(s),
77            Logger::None => Ok(()),
78        }
79    }
80}
81
82pub struct BootLogger {
83    logger: SingleThreaded<RefCell<Logger>>,
84    in_memory_logger: SingleThreaded<RefCell<Option<StringBuffer<'static>>>>,
85}
86
87pub static BOOT_LOGGER: BootLogger = BootLogger {
88    logger: SingleThreaded(RefCell::new(Logger::None)),
89    in_memory_logger: SingleThreaded(RefCell::new(None)),
90};
91
92/// Initialize the in-memory log buffer. This range must be identity mapped, and
93/// unused by anything else.
94pub fn boot_logger_memory_init(buffer: MemoryRange) {
95    if buffer.is_empty() {
96        return;
97    }
98
99    let log_buffer_ptr = buffer.start() as *mut u8;
100    // SAFETY: At file build time, this range is enforced to be unused by
101    // anything else. The rest of the bootshim will mark this range as reserved
102    // and not free to be used by anything else.
103    //
104    // The VA is valid as we are identity mapped.
105    let log_buffer_slice =
106        unsafe { core::slice::from_raw_parts_mut(log_buffer_ptr, buffer.len() as usize) };
107
108    *BOOT_LOGGER.in_memory_logger.borrow_mut() = Some(
109        StringBuffer::new(log_buffer_slice)
110            .expect("log buffer should be valid from fixed at build config"),
111    );
112}
113
114/// Initialize the runtime boot logger, for logging to serial or other outputs.
115///
116/// If a runtime logger was initialized, emit any in-memory log to the
117/// configured runtime output.
118pub fn boot_logger_runtime_init(isolation_type: IsolationType, com3_serial_available: ComInfo) {
119    let mut logger = BOOT_LOGGER.logger.borrow_mut();
120
121    *logger = match (isolation_type, com3_serial_available) {
122        #[cfg(target_arch = "x86_64")]
123        (IsolationType::None | IsolationType::Vbs, ComInfo::Ns16550 { current_speed, .. }) => {
124            init_serial(minimal_rt::arch::com3(), current_speed)
125                .map_or(Logger::None, Logger::Serial)
126        }
127        // TODO: fix the PL011 minimal_rt driver. Currently hangs even if
128        // the MMIO address is correctly configured.
129        // #[cfg(target_arch = "aarch64")]
130        // (IsolationType::None, ComInfo::Pl011 { .. }) => Logger::Serial(Serial::init()),
131        #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
132        (IsolationType::Tdx, ComInfo::Ns16550 { current_speed, .. }) => {
133            let backend = TdxSerialBackend(minimal_rt::arch::COM3);
134            // SAFETY: COM3 is a valid I/O port, which is used for debug output
135            // only.
136            let uart = unsafe { Uart16550::new_with_backend(backend) };
137            init_serial(uart, current_speed).map_or(Logger::None, Logger::TdxSerial)
138        }
139        #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
140        (IsolationType::Snp, ComInfo::Ns16550 { current_speed, .. }) => {
141            let backend = SnpSerialBackend(minimal_rt::arch::COM3);
142            // SAFETY: COM3 is a valid I/O port, which is used for debug output
143            // only.
144            let uart = unsafe { Uart16550::new_with_backend(backend) };
145            init_serial(uart, current_speed).map_or(Logger::None, Logger::SnpSerial)
146        }
147        _ => Logger::None,
148    };
149
150    // Emit any in-memory log to the runtime logger.
151    if let Some(buf) = BOOT_LOGGER.in_memory_logger.borrow_mut().as_mut() {
152        let _ = logger.write_str(buf.contents());
153    }
154}
155
156impl Write for &BootLogger {
157    fn write_str(&mut self, s: &str) -> fmt::Result {
158        if let Some(buf) = self.in_memory_logger.borrow_mut().as_mut() {
159            // Ignore the errors from the in memory logger.
160            let _ = buf.append(s);
161        }
162        self.logger.borrow_mut().write_str(s)
163    }
164}
165
166impl log::Log for BootLogger {
167    fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
168        // TODO: filter level
169        true
170    }
171
172    fn log(&self, record: &log::Record<'_>) {
173        let _ = writeln!(&*self, "[{}] {}", record.level(), record.args());
174    }
175
176    fn flush(&self) {}
177}