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::SnpIoAccess;
13#[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
14use crate::arch::tdx::TdxIoAccess;
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 = "x86_64")]
23use minimal_rt::arch::InstrIoAccess;
24use minimal_rt::arch::Serial;
25use string_page_buf::StringBuffer;
26
27enum Logger {
28    #[cfg(target_arch = "x86_64")]
29    Serial(Serial<InstrIoAccess>),
30    #[cfg(target_arch = "aarch64")]
31    #[expect(dead_code)]
32    Serial(Serial),
33    #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
34    TdxSerial(Serial<TdxIoAccess>),
35    #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
36    SnpSerial(Serial<SnpIoAccess>),
37    None,
38}
39
40impl Logger {
41    fn write_str(&mut self, s: &str) -> fmt::Result {
42        match self {
43            Logger::Serial(serial) => serial.write_str(s),
44            #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
45            Logger::TdxSerial(serial) => serial.write_str(s),
46            #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
47            Logger::SnpSerial(serial) => serial.write_str(s),
48            Logger::None => Ok(()),
49        }
50    }
51}
52
53pub struct BootLogger {
54    logger: SingleThreaded<RefCell<Logger>>,
55    in_memory_logger: SingleThreaded<RefCell<Option<StringBuffer<'static>>>>,
56}
57
58pub static BOOT_LOGGER: BootLogger = BootLogger {
59    logger: SingleThreaded(RefCell::new(Logger::None)),
60    in_memory_logger: SingleThreaded(RefCell::new(None)),
61};
62
63/// Initialize the in-memory log buffer. This range must be identity mapped, and
64/// unused by anything else.
65pub fn boot_logger_memory_init(buffer: MemoryRange) {
66    if buffer.is_empty() {
67        return;
68    }
69
70    let log_buffer_ptr = buffer.start() as *mut u8;
71    // SAFETY: At file build time, this range is enforced to be unused by
72    // anything else. The rest of the bootshim will mark this range as reserved
73    // and not free to be used by anything else.
74    //
75    // The VA is valid as we are identity mapped.
76    let log_buffer_slice =
77        unsafe { core::slice::from_raw_parts_mut(log_buffer_ptr, buffer.len() as usize) };
78
79    *BOOT_LOGGER.in_memory_logger.borrow_mut() = Some(
80        StringBuffer::new(log_buffer_slice)
81            .expect("log buffer should be valid from fixed at build config"),
82    );
83}
84
85/// Initialize the runtime boot logger, for logging to serial or other outputs.
86///
87/// If a runtime logger was initialized, emit any in-memory log to the
88/// configured runtime output.
89pub fn boot_logger_runtime_init(isolation_type: IsolationType, com3_serial_available: ComInfo) {
90    let mut logger = BOOT_LOGGER.logger.borrow_mut();
91
92    *logger = match (isolation_type, com3_serial_available) {
93        #[cfg(target_arch = "x86_64")]
94        (IsolationType::None, ComInfo::Ns16550 { .. }) => {
95            Logger::Serial(Serial::init(InstrIoAccess))
96        }
97        // TODO: fix the PL011 minimal_rt driver. Currently hangs even if
98        // the MMIO address is correctly configured.
99        // #[cfg(target_arch = "aarch64")]
100        // (IsolationType::None, ComInfo::Pl011 { .. }) => Logger::Serial(Serial::init()),
101        #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
102        (IsolationType::Tdx, ComInfo::Ns16550 { .. }) => {
103            Logger::TdxSerial(Serial::init(TdxIoAccess))
104        }
105        #[cfg(all(target_arch = "x86_64", feature = "cvm_boot_log"))]
106        (IsolationType::Snp, ComInfo::Ns16550 { .. }) => {
107            Logger::SnpSerial(Serial::init(SnpIoAccess))
108        }
109        _ => Logger::None,
110    };
111
112    // Emit any in-memory log to the runtime logger.
113    if let Some(buf) = BOOT_LOGGER.in_memory_logger.borrow_mut().as_mut() {
114        let _ = logger.write_str(buf.contents());
115    }
116}
117
118impl Write for &BootLogger {
119    fn write_str(&mut self, s: &str) -> fmt::Result {
120        if let Some(buf) = self.in_memory_logger.borrow_mut().as_mut() {
121            // Ignore the errors from the in memory logger.
122            let _ = buf.append(s);
123        }
124        self.logger.borrow_mut().write_str(s)
125    }
126}
127
128impl log::Log for BootLogger {
129    fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
130        // TODO: filter level
131        true
132    }
133
134    fn log(&self, record: &log::Record<'_>) {
135        let _ = writeln!(&*self, "[{}] {}", record.level(), record.args());
136    }
137
138    fn flush(&self) {}
139}