use core::{
cell::OnceCell,
ffi::{c_char, c_void},
sync::atomic::{AtomicBool, Ordering},
};
use alloc::string::ToString;
use patina::standard::efi;
use patina::{
BinaryGuid, Char8Str,
component::service::{Service, performance::PerformanceManager},
performance::{
error::Error,
measurement::{CallerIdentifier, PerfAttribute},
record::known::KnownPerfId,
},
protocol::ProtocolInterface,
};
pub const EDKII_PERFORMANCE_MEASUREMENT_PROTOCOL_GUID: BinaryGuid =
BinaryGuid::from_string("C85D06BE-5F75-48CE-A80F-1236BA3B87B1");
pub type CreateMeasurementUefi = unsafe extern "efiapi" fn(
caller_identifier: *const c_void,
guid: Option<&efi::Guid>,
string: *const c_char,
ticker: u64,
address: usize,
identifier: u32,
attribute: PerfAttribute,
) -> efi::Status;
#[repr(C)]
pub struct EdkiiPerformanceMeasurementProtocol {
pub create_performance_measurement: CreateMeasurementUefi,
}
unsafe impl ProtocolInterface for EdkiiPerformanceMeasurementProtocol {
const PROTOCOL_GUID: BinaryGuid = EDKII_PERFORMANCE_MEASUREMENT_PROTOCOL_GUID;
}
struct ServiceHolder {
service: OnceCell<Service<dyn PerformanceManager>>,
initializing: AtomicBool,
}
unsafe impl Sync for ServiceHolder {}
impl ServiceHolder {
const fn new() -> Self {
Self { service: OnceCell::new(), initializing: AtomicBool::new(false) }
}
fn set(&self, service: Service<dyn PerformanceManager>) -> Result<(), &'static str> {
if self.initializing.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_ok() {
let result = self.service.set(service).map_err(|_| "Performance service already set");
self.initializing.store(false, Ordering::Release);
return result;
}
Err("Performance service is currently being set elsewhere")
}
fn get(&self) -> Option<&Service<dyn PerformanceManager>> {
if self.initializing.load(Ordering::Acquire) { None } else { self.service.get() }
}
}
static PERF_SERVICE: ServiceHolder = ServiceHolder::new();
pub(crate) fn set_performance_service(service: Service<dyn PerformanceManager>) -> Result<(), &'static str> {
PERF_SERVICE.set(service)
}
#[cfg_attr(coverage, coverage(off))]
pub(crate) unsafe extern "efiapi" fn create_performance_measurement_efiapi(
caller_identifier: *const c_void,
guid: Option<&efi::Guid>,
string: *const c_char,
ticker: u64,
address: usize,
identifier: u32,
attribute: PerfAttribute,
) -> efi::Status {
let string =
unsafe { string.as_ref().map(|s| Char8Str::from_ptr(core::ptr::from_ref::<c_char>(s).cast()).to_string()) };
if identifier > u32::from(u16::MAX) {
log::error!("Performance: Invalid identifier passed to create_performance_measurement_efiapi: {identifier}");
return efi::Status::INVALID_PARAMETER;
}
let perf_id = match KnownPerfId::normalize_perf_id(
identifier as u16,
caller_identifier.cast_mut(),
string.as_ref(),
attribute,
) {
Ok(perf_id) => perf_id,
Err(status) => return status,
};
let is_guid = CallerIdentifier::perf_id_is_guid(perf_id);
let caller_identifier = unsafe {
match CallerIdentifier::from_ptr(caller_identifier, is_guid) {
Some(v) => v,
None => return efi::Status::INVALID_PARAMETER,
}
};
let Some(service) = PERF_SERVICE.get() else {
log::error!("Performance: create_performance_measurement_efiapi called before service registration.");
return efi::Status::NOT_READY;
};
match service.create_measurement(caller_identifier, guid, string.as_deref(), ticker, address, perf_id, attribute) {
Ok(()) => efi::Status::SUCCESS,
Err(Error::OutOfResources) => efi::Status::OUT_OF_RESOURCES,
Err(Error::Efi(status_code)) => {
log::error!(
"Performance: Something went wrong in create_performance_measurement. status_code: {status_code:?}"
);
status_code.into()
}
Err(error) => {
log::error!("Performance: Something went wrong in create_performance_measurement. Error: {error}");
efi::Status::ABORTED
}
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use super::*;
use patina::component::service::performance::MockPerformanceManager;
fn mock_service() -> Service<dyn PerformanceManager> {
Service::mock(Box::new(MockPerformanceManager::new()))
}
#[test]
fn test_service_holder_set_get_lifecycle() {
let holder = ServiceHolder::new();
assert!(holder.get().is_none());
assert!(holder.set(mock_service()).is_ok());
assert!(holder.get().is_some());
assert_eq!(holder.set(mock_service()), Err("Performance service already set"));
holder.initializing.store(true, Ordering::Release);
assert_eq!(holder.set(mock_service()), Err("Performance service is currently being set elsewhere"));
assert!(holder.get().is_none());
}
}