diff --git a/openhcl/bootloader_fdt_parser/src/lib.rs b/openhcl/bootloader_fdt_parser/src/lib.rs index 55296ad999..43f6883add 100644 --- a/openhcl/bootloader_fdt_parser/src/lib.rs +++ b/openhcl/bootloader_fdt_parser/src/lib.rs @@ -173,6 +173,8 @@ pub struct ParsedBootDtInfo { /// VTL2 range for private pool memory. #[inspect(iter_by_index)] pub private_pool_ranges: Vec, + /// The text boot log reported by the bootloader. + pub boot_log: String, } fn err_to_owned(e: fdt::parser::Error<'_>) -> anyhow::Error { @@ -211,6 +213,7 @@ struct OpenhclInfo { memory_allocation_mode: MemoryAllocationMode, isolation: IsolationType, private_pool_ranges: Vec, + boot_log: String, } fn parse_memory_openhcl(node: &Node<'_>) -> anyhow::Result { @@ -347,6 +350,12 @@ fn parse_openhcl(node: &Node<'_>) -> anyhow::Result { memory.sort_by_key(|r| r.range().start()); accepted_memory.sort_by_key(|r| r.start()); + let boot_log = try_find_property(node, "boot-log") + .context("missing boot-log")? + .read_str() + .map_err(err_to_owned)? + .to_string(); + // Report config ranges in a separate vec as well, for convenience. let config_ranges = memory .iter() @@ -420,6 +429,7 @@ fn parse_openhcl(node: &Node<'_>) -> anyhow::Result { memory_allocation_mode, isolation, private_pool_ranges, + boot_log, }) } @@ -514,6 +524,7 @@ impl ParsedBootDtInfo { let mut isolation = IsolationType::None; let mut vtl2_reserved_range = MemoryRange::EMPTY; let mut private_pool_ranges = Vec::new(); + let mut boot_log = String::new(); let parser = Parser::new(raw) .map_err(err_to_owned) @@ -548,6 +559,7 @@ impl ParsedBootDtInfo { memory_allocation_mode: n_memory_allocation_mode, isolation: n_isolation, private_pool_ranges: n_private_pool_ranges, + boot_log: n_boot_log, } = parse_openhcl(&child)?; vtl0_mmio = n_vtl0_mmio; config_ranges = n_config_ranges; @@ -558,6 +570,7 @@ impl ParsedBootDtInfo { isolation = n_isolation; vtl2_reserved_range = n_vtl2_reserved_range; private_pool_ranges = n_private_pool_ranges; + boot_log = n_boot_log; } _ if child.name.starts_with("memory@") => { @@ -591,6 +604,7 @@ impl ParsedBootDtInfo { isolation, vtl2_reserved_range, private_pool_ranges, + boot_log, }) } } @@ -795,6 +809,9 @@ mod tests { openhcl_builder = openhcl_builder.add_u64(p_vtl0_alias_map, data)?; } + let p_boot_log = openhcl_builder.add_string("boot-log")?; + openhcl_builder = openhcl_builder.add_str(p_boot_log, &info.boot_log)?; + openhcl_builder = openhcl_builder .start_node("vmbus-vtl0")? .add_u32(p_address_cells, 2)? @@ -962,6 +979,7 @@ mod tests { range: MemoryRange::new(0x60000..0x70000), vnode: 0, }], + boot_log: "hello world".to_string(), }; let dt = build_dt(&orig_info).unwrap(); diff --git a/openhcl/openhcl_boot/src/boot_logger.rs b/openhcl/openhcl_boot/src/boot_logger.rs index f4e732271f..45ed752f67 100644 --- a/openhcl/openhcl_boot/src/boot_logger.rs +++ b/openhcl/openhcl_boot/src/boot_logger.rs @@ -12,6 +12,7 @@ use crate::arch::tdx::TdxIoAccess; use crate::host_params::shim_params::IsolationType; use crate::single_threaded::SingleThreaded; +use arrayvec::ArrayString; use core::cell::RefCell; use core::fmt; use core::fmt::Write; @@ -46,12 +47,16 @@ impl Logger { } } +const MAX_LOG_SIZE: usize = 4096 * 4; + pub struct BootLogger { logger: SingleThreaded>, + memory_log: SingleThreaded>>, } pub static BOOT_LOGGER: BootLogger = BootLogger { logger: SingleThreaded(RefCell::new(Logger::None)), + memory_log: SingleThreaded(RefCell::new(ArrayString::new_const())), }; /// Initialize the boot logger. This replaces any previous init calls. @@ -74,10 +79,26 @@ pub fn boot_logger_init(isolation_type: IsolationType, logger_type: LoggerType) impl Write for &BootLogger { fn write_str(&mut self, s: &str) -> fmt::Result { + if let Ok(mut memory_log) = self.memory_log.try_borrow_mut() { + // TODO: Use a circular buffer instead of just ignoring logs after + // the buffer is full. This requires some other kind of + // non-allocating circular buffer, as arrayvec doesn't have any such + // data structures. + let _ = memory_log.write_str(s); + } + self.logger.borrow_mut().write_str(s) } } +impl BootLogger { + /// Get the current log buffer. This holds a borrow on the memory buffer, so + /// until this is dropped no new messages will be added. + pub fn log_buffer(&self) -> core::cell::Ref<'_, ArrayString> { + self.memory_log.borrow() + } +} + /// Log a message. These messages are always emitted regardless of debug or /// release, if a corresponding logger was configured. /// diff --git a/openhcl/openhcl_boot/src/cmdline.rs b/openhcl/openhcl_boot/src/cmdline.rs index 4432fcf402..b5266d8633 100644 --- a/openhcl/openhcl_boot/src/cmdline.rs +++ b/openhcl/openhcl_boot/src/cmdline.rs @@ -13,6 +13,9 @@ use underhill_confidentiality::OPENHCL_CONFIDENTIAL_DEBUG_ENV_VAR_NAME; const BOOT_LOG: &str = "OPENHCL_BOOT_LOG="; const SERIAL_LOGGER: &str = "com3"; +/// Enable boot log to be reported to usermode via the device tree. +const REPORT_BOOT_LOG: &str = "OPENHCL_REPORT_BOOT_LOG=1"; + /// Enable the private VTL2 GPA pool for page allocations. This is only enabled /// via the command line, because in order to support the VTL2 GPA pool /// generically, the boot shim must read serialized data from the previous @@ -30,6 +33,7 @@ pub struct BootCommandLineOptions { pub logger: Option, pub confidential_debug: bool, pub enable_vtl2_gpa_pool: Option, + pub report_boot_log: bool, } /// Parse arguments from a command line. @@ -38,6 +42,7 @@ pub fn parse_boot_command_line(cmdline: &str) -> BootCommandLineOptions { logger: None, confidential_debug: false, enable_vtl2_gpa_pool: None, + report_boot_log: false, }; for arg in cmdline.split_whitespace() { @@ -66,6 +71,8 @@ pub fn parse_boot_command_line(cmdline: &str) -> BootCommandLineOptions { Some(num) } }); + } else if arg.starts_with(REPORT_BOOT_LOG) { + result.report_boot_log = true; } } @@ -83,7 +90,8 @@ mod tests { BootCommandLineOptions { logger: Some(LoggerType::Serial), confidential_debug: false, - enable_vtl2_gpa_pool: None + enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); @@ -92,7 +100,8 @@ mod tests { BootCommandLineOptions { logger: None, confidential_debug: false, - enable_vtl2_gpa_pool: None + enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); @@ -101,7 +110,8 @@ mod tests { BootCommandLineOptions { logger: None, confidential_debug: false, - enable_vtl2_gpa_pool: None + enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); @@ -110,7 +120,8 @@ mod tests { BootCommandLineOptions { logger: None, confidential_debug: false, - enable_vtl2_gpa_pool: None + enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); @@ -119,7 +130,8 @@ mod tests { BootCommandLineOptions { logger: None, confidential_debug: false, - enable_vtl2_gpa_pool: None + enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); @@ -129,7 +141,8 @@ mod tests { BootCommandLineOptions { logger: Some(LoggerType::Serial), confidential_debug: true, - enable_vtl2_gpa_pool: None + enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); } @@ -142,6 +155,7 @@ mod tests { logger: None, confidential_debug: false, enable_vtl2_gpa_pool: Some(1), + report_boot_log: false, } ); assert_eq!( @@ -150,6 +164,7 @@ mod tests { logger: None, confidential_debug: false, enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); assert_eq!( @@ -158,6 +173,7 @@ mod tests { logger: None, confidential_debug: false, enable_vtl2_gpa_pool: None, + report_boot_log: false, } ); assert_eq!( @@ -166,6 +182,20 @@ mod tests { logger: None, confidential_debug: false, enable_vtl2_gpa_pool: Some(1024), + report_boot_log: false, + } + ); + } + + #[test] + fn test_report_boot_log() { + assert_eq!( + parse_boot_command_line("OPENHCL_REPORT_BOOT_LOG=1"), + BootCommandLineOptions { + logger: None, + confidential_debug: false, + enable_vtl2_gpa_pool: None, + report_boot_log: true, } ); } diff --git a/openhcl/openhcl_boot/src/dt.rs b/openhcl/openhcl_boot/src/dt.rs index bf25b1fb86..ef09f8d585 100644 --- a/openhcl/openhcl_boot/src/dt.rs +++ b/openhcl/openhcl_boot/src/dt.rs @@ -158,6 +158,7 @@ pub fn write_dt( cmdline: &ArrayString, sidecar: Option<&SidecarConfig<'_>>, boot_times: Option, + report_boot_log: bool, ) -> Result<(), DtError> { // First, the reservation map is built. That keyes off of the x86 E820 memory map. // The `/memreserve/` is used to tell the kernel that the reserved memory is RAM @@ -461,6 +462,20 @@ pub fn write_dt( }; openhcl_builder = openhcl_builder.add_str(p_isolation_type, isolation_type)?; + // If requested, capture the current memory log and report it to usermode. + // + // NOTE: This log is not all-inclusive, as any logs after this point are not + // saved to the device tree. + let p_boot_log = openhcl_builder.add_string("boot-log")?; + if report_boot_log { + openhcl_builder = openhcl_builder.add_str( + p_boot_log, + crate::boot_logger::BOOT_LOGGER.log_buffer().as_str(), + )?; + } else { + openhcl_builder = openhcl_builder.add_str(p_boot_log, "")?; + } + // Indicate what kind of memory allocation mode was done by the bootloader // to usermode. let p_memory_allocation_mode = openhcl_builder.add_string("memory-allocation-mode")?; diff --git a/openhcl/openhcl_boot/src/main.rs b/openhcl/openhcl_boot/src/main.rs index 8f24903336..9b3fe0ca7d 100644 --- a/openhcl/openhcl_boot/src/main.rs +++ b/openhcl/openhcl_boot/src/main.rs @@ -581,6 +581,7 @@ fn shim_main(shim_params_raw_offset: isize) -> ! { boot_logger_init(p.isolation_type, typ); log!("openhcl_boot: early debugging enabled"); } + let mut report_boot_log = static_options.report_boot_log; let can_trust_host = p.isolation_type == IsolationType::None || static_options.confidential_debug; @@ -626,6 +627,8 @@ fn shim_main(shim_params_raw_offset: isize) -> ! { // if it wasn't otherwise requested. boot_logger_init(p.isolation_type, LoggerType::Serial); } + + report_boot_log |= dynamic_options.report_boot_log; } log!("openhcl_boot: entered shim_main"); @@ -748,6 +751,7 @@ fn shim_main(shim_params_raw_offset: isize) -> ! { &cmdline, sidecar.as_ref(), boot_times, + report_boot_log, ) .unwrap(); @@ -939,6 +943,7 @@ mod test { &ArrayString::from("test").unwrap_or_default(), None, None, + false, ) .unwrap(); } @@ -1012,6 +1017,7 @@ mod test { &ArrayString::from("test").unwrap_or_default(), None, None, + false, ) .unwrap(); @@ -1040,6 +1046,7 @@ mod test { &ArrayString::from("test").unwrap_or_default(), None, None, + false, ) .unwrap();