From 918e89968acf9d9374fae0846be918ab4b73d992 Mon Sep 17 00:00:00 2001 From: Christopher Zurcher Date: Mon, 25 Nov 2024 15:32:24 -0800 Subject: [PATCH] MsWheaPkg: Add Rust Telemetry helper library --- Cargo.toml | 2 + .../RustMuTelemetryHelperLib/Cargo.toml | 24 ++ .../RustMuTelemetryHelperLib/src/lib.rs | 229 ++++++++++++++++++ .../src/status_code_runtime.rs | 85 +++++++ 4 files changed, 340 insertions(+) create mode 100644 MsWheaPkg/Crates/RustMuTelemetryHelperLib/Cargo.toml create mode 100644 MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/lib.rs create mode 100644 MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/status_code_runtime.rs diff --git a/Cargo.toml b/Cargo.toml index 54a761f787..63f58f544e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ hidparser = {git = "https://github.com/microsoft/mu_rust_hid.git", branch = "mai HiiKeyboardLayout = {path = "HidPkg/Crates/HiiKeyboardLayout"} mu_rust_helpers = { git = "https://github.com/microsoft/mu_rust_helpers.git", tag = "v1.1.0" } boot_services = { git = "https://github.com/microsoft/mu_rust_helpers.git", tag = "v1.2.1" } +RustMuTelemetryHelperLib = {path = "MsWheaPkg/Crates/RustMuTelemetryHelperLib"} memoffset = "0.9.0" num-traits = { version = "0.2", default-features = false} @@ -27,3 +28,4 @@ r-efi = "5.0.0" rustversion = "1.0.14" spin = "0.9.8" scroll = { version = "0.12", default-features = false, features = ["derive"]} +uuid = { version = "1.10.0", default-features = false} diff --git a/MsWheaPkg/Crates/RustMuTelemetryHelperLib/Cargo.toml b/MsWheaPkg/Crates/RustMuTelemetryHelperLib/Cargo.toml new file mode 100644 index 0000000000..b6fefe4f64 --- /dev/null +++ b/MsWheaPkg/Crates/RustMuTelemetryHelperLib/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "RustMuTelemetryHelperLib" +version = "0.1.0" +edition = "2021" + +[lib] +name = "rust_mu_telemetry_helper_lib" +path = "src/lib.rs" + +[dependencies] +boot_services = { workspace = true } +mu_pi = { workspace = true } +mu_rust_helpers = { workspace = true } +r-efi = { workspace = true } +RustAdvancedLoggerDxe = { workspace = true } +uuid = { workspace = true } + +[dev-dependencies] +boot_services = { workspace = true, features = ["mockall"] } +RustAdvancedLoggerDxe = { workspace = true, features = ["std"] } +mockall = { version = "0.13.0" } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] } diff --git a/MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/lib.rs b/MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/lib.rs new file mode 100644 index 0000000000..eb137cc8e8 --- /dev/null +++ b/MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/lib.rs @@ -0,0 +1,229 @@ +//! Rust MU Telemetry Helper +//! +//! Rust helper library for logging telemetry. +//! +//! ## Examples and Usage +//! +//! ```no_run +//! use rust_mu_telemetry_helper_lib::{init_telemetry, log_telemetry}; +//! use r_efi::efi; +//! pub extern "efiapi" fn efi_main( +//! _image_handle: efi::Handle, +//! system_table: *const system::SystemTable, +//! ) -> efi::Status { +//! +//! //Initialize Boot Services +//! init_telemetry((*system_table).boot_services.as_ref().unwrap()); +//! +//! if (some_failure) { +//! let _ = log_telemetry(false, 0xA1A2A3A4, 0xB1B2B3B4B5B6B7B8, 0xC1C2C3C4C5C6C7C8, None, None, None); +//! } +//! +//! efi::Status::SUCCESS +//! } +//! ``` +//! +//! ## License +//! +//! Copyright (C) Microsoft Corporation. All rights reserved. +//! +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! +#![cfg_attr(target_os = "uefi", no_std)] + +mod status_code_runtime; + +use mu_pi::protocols::status_code::{EfiStatusCodeType, EfiStatusCodeValue}; +use mu_pi::status_code::{EFI_ERROR_CODE, EFI_ERROR_MAJOR, EFI_ERROR_MINOR}; +use mu_rust_helpers::{ + boot_services::{BootServices, StandardBootServices}, + guid, + guid::guid, +}; +use r_efi::efi; +use status_code_runtime::{ReportStatusCode, StatusCodeRuntimeProtocol}; +use uuid::uuid; + +static BOOT_SERVICES: StandardBootServices = StandardBootServices::new_uninit(); + +/// Matches gMsWheaRSCDataTypeGuid in MsWheaPkg\MsWheaPkg.dec +/// Matches MS_WHEA_RSC_DATA_TYPE in MsWheaPkg\Private\Guid\MsWheaReportDataType.h +const MS_WHEA_RSC_DATA_TYPE_GUID: efi::Guid = guid!("91DEEA05-8C0A-4DCD-B91E-F21CA0C68405"); + +const MS_WHEA_ERROR_STATUS_TYPE_INFO: EfiStatusCodeType = EFI_ERROR_MINOR | EFI_ERROR_CODE; +const MS_WHEA_ERROR_STATUS_TYPE_FATAL: EfiStatusCodeType = EFI_ERROR_MAJOR | EFI_ERROR_CODE; + +/** + Internal RSC Extended Data Buffer format used by Project Mu firmware WHEA infrastructure. + + A Buffer of this format should be passed to ReportStatusCodeWithExtendedData + + LibraryID: GUID of the library reporting the error. If not from a library use zero guid + IhvSharingGuid: GUID of the partner to share this with. If none use zero guid + AdditionalInfo1: 64 bit value used for caller to include necessary interrogative information + AdditionalInfo2: 64 bit value used for caller to include necessary interrogative information +**/ +// #pragma pack(1) +// typedef struct { +// EFI_GUID LibraryID; +// EFI_GUID IhvSharingGuid; +// UINT64 AdditionalInfo1; +// UINT64 AdditionalInfo2; +// } MS_WHEA_RSC_INTERNAL_ERROR_DATA; +// #pragma pack() + +#[repr(C)] +struct MsWheaRscInternalErrorData { + library_id: efi::Guid, + ihv_sharing_guid: efi::Guid, + additional_info_1: u64, + additional_info_2: u64, +} + +/// Log telemetry +/// +/// @param[in] ClassId An EFI_STATUS_CODE_VALUE representing the event that has occurred. This +/// value will occupy the same space as EventId from LogCriticalEvent(), and +/// should be unique enough to identify a module or region of code. +/// @param[in] ExtraData1 [Optional] This should be data specific to the cause. Ideally, used to contain contextual +/// or runtime data related to the event (e.g. register contents, failure codes, etc.). +/// It will be persisted. +/// @param[in] ExtraData2 [Optional] Another UINT64 similar to ExtraData1. +/// @param[in] ComponentId [Optional] This identifier should uniquely identify the module that is emitting this +/// event. When this is passed in as NULL, report status code will automatically populate +/// this field with gEfiCallerIdGuid. +/// @param[in] LibraryId This should identify the library that is emitting this event. +/// @param[in] IhvId This should identify the Ihv related to this event if applicable. For example, +/// this would typically be used for TPM and SOC specific events. +#[cfg(not(tarpaulin_include))] +pub fn log_telemetry( + is_fatal: bool, + class_id: EfiStatusCodeValue, + extra_data1: u64, + extra_data2: u64, + component_id: Option<&efi::Guid>, + library_id: Option<&efi::Guid>, + ihv_id: Option<&efi::Guid>, +) -> Result<(), efi::Status> { + log_telemetry_internal( + &BOOT_SERVICES, + is_fatal, + class_id, + extra_data1, + extra_data2, + component_id, + library_id, + ihv_id, + ) +} + +fn log_telemetry_internal( + boot_services: &B, + is_fatal: bool, + class_id: EfiStatusCodeValue, + extra_data1: u64, + extra_data2: u64, + component_id: Option<&efi::Guid>, + library_id: Option<&efi::Guid>, + ihv_id: Option<&efi::Guid>, +) -> Result<(), efi::Status> { + let status_code_type: EfiStatusCodeType = + if is_fatal { MS_WHEA_ERROR_STATUS_TYPE_FATAL } else { MS_WHEA_ERROR_STATUS_TYPE_INFO }; + + let error_data = MsWheaRscInternalErrorData { + library_id: *library_id.unwrap_or(&guid::ZERO), + ihv_sharing_guid: *ihv_id.unwrap_or(&guid::ZERO), + additional_info_1: extra_data1, + additional_info_2: extra_data2, + }; + + StatusCodeRuntimeProtocol::report_status_code( + boot_services, + status_code_type, + class_id, + 0, + component_id, + MS_WHEA_RSC_DATA_TYPE_GUID, + error_data, + ) +} + +#[cfg(not(tarpaulin_include))] +pub fn init_telemetry(efi_boot_services: &efi::BootServices) { + BOOT_SERVICES.initialize(efi_boot_services) +} + +#[cfg(test)] +mod test { + use boot_services::MockBootServices; + use mu_pi::protocols::status_code; + use mu_pi::protocols::status_code::{EfiStatusCodeData, EfiStatusCodeType, EfiStatusCodeValue}; + use mu_rust_helpers::guid::{guid, guid_fmt}; + use r_efi::efi; + use rust_advanced_logger_dxe::{debugln, DEBUG_INFO}; + use uuid::uuid; + + use crate::{ + log_telemetry_internal, + status_code_runtime::StatusCodeRuntimeProtocol, + MsWheaRscInternalErrorData, MS_WHEA_ERROR_STATUS_TYPE_FATAL, + }; + use core::mem::size_of; + + const DATA_SIZE: usize = size_of::() + size_of::(); + const MOCK_CALLER_ID: efi::Guid = guid!("d0d1d2d3-d4d5-d6d7-d8d9-dadbdcdddedf"); + const MOCK_STATUS_CODE_VALUE: EfiStatusCodeValue = 0xa0a1a2a3; + + extern "efiapi" fn mock_report_status_code( + r#type: EfiStatusCodeType, + value: EfiStatusCodeValue, + instance: u32, + caller_id: *const efi::Guid, // Optional + _data: *const EfiStatusCodeData, // Optional + ) -> efi::Status { + assert_eq!(r#type, MS_WHEA_ERROR_STATUS_TYPE_FATAL); + assert_eq!(value, MOCK_STATUS_CODE_VALUE); + assert_eq!(instance, 0); + assert_eq!(unsafe { *caller_id }, MOCK_CALLER_ID); + debugln!(DEBUG_INFO, "[MockStatusCodeRuntime] caller_id: {}", guid_fmt!(unsafe { *caller_id })); + efi::Status::SUCCESS + } + + static MOCK_STATUS_CODE_RUNTIME_INTERFACE: status_code::Protocol = + status_code::Protocol { report_status_code: mock_report_status_code }; + + #[test] + fn try_log_telemetry() { + let mut mock_boot_services: MockBootServices = MockBootServices::new(); + + mock_boot_services.expect_locate_protocol().returning(|_: &StatusCodeRuntimeProtocol, registration| unsafe { + assert_eq!(registration, None); + Ok(Some( + (&MOCK_STATUS_CODE_RUNTIME_INTERFACE as *const status_code::Protocol + as *mut status_code::Protocol) + .as_mut() + .unwrap(), + )) + }); + + // Test sizes of "repr(C)" structs + assert_eq!(size_of::(), 48); + assert_eq!(size_of::<[u8; 68]>(), DATA_SIZE); + + // Test Deref trait + assert_eq!(*StatusCodeRuntimeProtocol, status_code::PROTOCOL_GUID); + assert_eq!( + Ok(()), + log_telemetry_internal( + &mock_boot_services, + true, + MOCK_STATUS_CODE_VALUE, + 0xb0b1b2b3b4b5b6b7, + 0xc0c1c2c3c4c5c6c7, + Some(&MOCK_CALLER_ID), + Some(&guid!("e0e1e2e3-e4e5-e6e7-e8e9-eaebecedeeef")), + Some(&guid!("f0f1f2f3-f4f5-f6f7-f8f9-fafbfcfdfeff")) + ) + ); + } +} diff --git a/MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/status_code_runtime.rs b/MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/status_code_runtime.rs new file mode 100644 index 0000000000..a48689f037 --- /dev/null +++ b/MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/status_code_runtime.rs @@ -0,0 +1,85 @@ +extern crate alloc; + +use core::{mem, ops::Deref, slice}; + +use boot_services::{protocol_handler::Protocol, BootServices}; +use mu_pi::protocols::status_code; +use mu_pi::protocols::status_code::{EfiStatusCodeData, EfiStatusCodeType, EfiStatusCodeValue}; +use mu_rust_helpers::guid; +use r_efi::efi; +use rust_advanced_logger_dxe::{debugln, DEBUG_INFO}; + +pub struct StatusCodeRuntimeProtocol; + +impl Deref for StatusCodeRuntimeProtocol { + type Target = efi::Guid; + + fn deref(&self) -> &Self::Target { + self.protocol_guid() + } +} + +unsafe impl Protocol for StatusCodeRuntimeProtocol { + type Interface = status_code::Protocol; + + fn protocol_guid(&self) -> &'static efi::Guid { + &status_code::PROTOCOL_GUID + } +} + +unsafe fn any_as_u8_slice(p: &T) -> &[u8] { + slice::from_raw_parts((p as *const T) as *const u8, mem::size_of::()) +} + +/// Rust interface for Report Status Code +pub trait ReportStatusCode { + fn report_status_code( + boot_services: &B, + status_code_type: EfiStatusCodeType, + status_code_value: EfiStatusCodeValue, + instance: u32, + caller_id: Option<&efi::Guid>, + data_type: efi::Guid, + data: T, + ) -> Result<(), efi::Status>; +} + +impl ReportStatusCode for StatusCodeRuntimeProtocol { + fn report_status_code( + boot_services: &B, + status_code_type: EfiStatusCodeType, + status_code_value: EfiStatusCodeValue, + instance: u32, + caller_id: Option<&efi::Guid>, + data_type: efi::Guid, + data: T, + ) -> Result<(), efi::Status> { + let protocol = boot_services.locate_protocol(&StatusCodeRuntimeProtocol, None)?; + if protocol.is_none() { + return Err(efi::Status::NOT_FOUND); + } + + let header_size = mem::size_of::(); + let data_size = mem::size_of::(); + + let header = EfiStatusCodeData { header_size: header_size as u16, size: data_size as u16, r#type: data_type }; + + let mut data_buffer = Vec::from(unsafe { any_as_u8_slice(&header) }); + data_buffer.extend(unsafe { any_as_u8_slice(&data) }); + + let data_ptr: *mut EfiStatusCodeData = data_buffer.as_mut_ptr() as *mut EfiStatusCodeData; + + let caller_id = caller_id.or(Some(&guid::CALLER_ID)).unwrap(); + + debugln!(DEBUG_INFO, "[RustStatusCodeRuntime] caller_id: {}", guid::guid_fmt!(caller_id)); + + let status = + (protocol.unwrap().report_status_code)(status_code_type, status_code_value, instance, caller_id, data_ptr); + + if status.is_error() { + Err(status) + } else { + Ok(()) + } + } +}