-
Notifications
You must be signed in to change notification settings - Fork 105
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MsWheaPkg: Add Rust Telemetry helper library
- Loading branch information
Showing
4 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)'] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<B: BootServices>( | ||
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::<EfiStatusCodeData>() + size_of::<MsWheaRscInternalErrorData>(); | ||
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::<MsWheaRscInternalErrorData>(), 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")) | ||
) | ||
); | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
MsWheaPkg/Crates/RustMuTelemetryHelperLib/src/status_code_runtime.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T: Sized>(p: &T) -> &[u8] { | ||
slice::from_raw_parts((p as *const T) as *const u8, mem::size_of::<T>()) | ||
} | ||
|
||
/// Rust interface for Report Status Code | ||
pub trait ReportStatusCode { | ||
fn report_status_code<T, B: BootServices>( | ||
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<T, B: BootServices>( | ||
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::<EfiStatusCodeData>(); | ||
let data_size = mem::size_of::<T>(); | ||
|
||
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(()) | ||
} | ||
} | ||
} |