use core::{
mem, ptr,
sync::atomic::{AtomicPtr, Ordering},
};
use patina::performance::{
self,
error::Error,
record::{PerformanceRecord, PerformanceRecordBuffer},
};
use scroll::Pwrite;
const PUBLISHED_FBPT_EXTRA_SPACE: usize = 0x40_000;
#[derive(Debug)]
pub(crate) struct Fbpt {
fbpt_address: usize,
_length: (u32, AtomicPtr<u32>),
other_records: PerformanceRecordBuffer,
}
impl Fbpt {
pub const SIGNATURE: u32 = patina::signature!('F', 'B', 'P', 'T');
pub const fn new() -> Self {
Self {
fbpt_address: 0,
_length: (Self::size_of_empty_table() as u32, AtomicPtr::new(ptr::null_mut())),
other_records: PerformanceRecordBuffer::new(),
}
}
pub fn length(&self) -> &u32 {
unsafe { self._length.1.load(Ordering::Relaxed).as_ref() }.unwrap_or(&self._length.0)
}
fn length_mut(&mut self) -> &mut u32 {
unsafe { self._length.1.load(Ordering::Relaxed).as_mut() }.unwrap_or(&mut self._length.0)
}
const fn size_of_empty_table() -> usize {
mem::size_of::<u32>() + mem::size_of::<u32>() + performance::record::PERFORMANCE_RECORD_HEADER_SIZE
+ FirmwareBasicBootPerfDataRecord::data_size()
}
}
impl Fbpt {
#[cfg(test)]
pub fn fbpt_address(&self) -> usize {
self.fbpt_address
}
#[cfg(test)]
pub fn perf_records(&self) -> &PerformanceRecordBuffer {
&self.other_records
}
pub fn set_perf_records(&mut self, perf_records: PerformanceRecordBuffer) {
*self.length_mut() = (Self::size_of_empty_table() + perf_records.size()) as u32;
self.other_records = perf_records;
}
pub fn add_record<T: PerformanceRecord>(&mut self, record: T) -> Result<(), Error> {
let record_size = self.other_records.push_record(record)?;
*self.length_mut() += record_size as u32;
Ok(())
}
pub fn published_table_size(&self) -> usize {
Self::size_of_empty_table() + self.other_records.size() + PUBLISHED_FBPT_EXTRA_SPACE
}
pub fn publish_table(&mut self, buffer: &'static mut [u8]) -> Result<usize, Error> {
self.fbpt_address = buffer.as_ptr() as usize;
let mut offset = 0;
buffer.gwrite(Self::SIGNATURE, &mut offset).map_err(|_| Error::BufferTooSmall)?;
let length_ptr = unsafe { buffer.as_ptr().byte_add(offset) } as *mut u32;
buffer.gwrite(*self.length(), &mut offset).map_err(|_| Error::BufferTooSmall)?;
FirmwareBasicBootPerfDataRecord::new().write_into(buffer, &mut offset).map_err(|_| Error::BufferTooSmall)?;
debug_assert_eq!(Self::size_of_empty_table(), offset);
self.other_records.report(buffer.get_mut(offset..).ok_or(Error::BufferTooSmall)?)?;
self._length.1.store(length_ptr, Ordering::Relaxed);
Ok(self.fbpt_address)
}
}
impl Default for Fbpt {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
#[repr(C)]
pub(crate) struct FirmwareBasicBootPerfDataRecord {
pub reset_end: u64,
pub os_loader_load_image_start: u64,
pub os_loader_start_image_start: u64,
pub exit_boot_services_entry: u64,
pub exit_boot_services_exit: u64,
}
impl FirmwareBasicBootPerfDataRecord {
const TYPE: u16 = 2;
const REVISION: u8 = 2;
pub const fn new() -> Self {
Self {
reset_end: 0,
os_loader_load_image_start: 0,
os_loader_start_image_start: 0,
exit_boot_services_entry: 0,
exit_boot_services_exit: 0,
}
}
const fn data_size() -> usize {
4 + mem::size_of::<Self>()
}
}
impl PerformanceRecord for FirmwareBasicBootPerfDataRecord {
fn record_type(&self) -> u16 {
Self::TYPE
}
fn revision(&self) -> u8 {
Self::REVISION
}
fn write_data_into(&self, buff: &mut [u8], offset: &mut usize) -> Result<(), Error> {
if buff.gwrite_with([0_u8; 4], offset, scroll::NATIVE).is_err()
|| buff.gwrite_with(self.reset_end, offset, scroll::NATIVE).is_err()
|| buff.gwrite_with(self.os_loader_load_image_start, offset, scroll::NATIVE).is_err()
|| buff.gwrite_with(self.os_loader_start_image_start, offset, scroll::NATIVE).is_err()
|| buff.gwrite_with(self.exit_boot_services_entry, offset, scroll::NATIVE).is_err()
|| buff.gwrite_with(self.exit_boot_services_exit, offset, scroll::NATIVE).is_err()
{
return Err(Error::Serialization);
}
Ok(())
}
}
impl Default for FirmwareBasicBootPerfDataRecord {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[cfg_attr(coverage, coverage(off))]
mod tests {
use super::*;
use core::{assert_eq, slice, unreachable};
use scroll::Pread;
use patina::performance::record::{
PERFORMANCE_RECORD_HEADER_SIZE,
extended::{
DualGuidStringEventRecord, DynamicStringEventRecord, GuidEventRecord, GuidQwordEventRecord,
GuidQwordStringEventRecord,
},
};
#[test]
fn test_set_perf_records() {
let mut performance_record_buffer = PerformanceRecordBuffer::new();
crate::performance::push_generic_record(&mut performance_record_buffer, 1, 1, &[0_u8; 16]);
let mut fbpt = Fbpt::new();
assert_eq!(&56, fbpt.length());
fbpt.set_perf_records(performance_record_buffer);
assert_eq!(&76, fbpt.length());
}
#[test]
fn test_serialize_into() {
let buffer: &'static mut [u8] = alloc::boxed::Box::leak(alloc::vec![0u8; 1000].into_boxed_slice());
let address = buffer.as_ptr() as usize;
let mut fbpt = Fbpt::new();
let guid = patina::BinaryGuid::ZERO;
fbpt.add_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
fbpt.add_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
fbpt.publish_table(buffer).unwrap();
assert_eq!(address, fbpt.fbpt_address);
fbpt.add_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test")).unwrap();
fbpt.add_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
fbpt.add_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
for (i, record) in fbpt.perf_records().iter().enumerate() {
let actual = (record.header.record_type, record.header.revision);
match i {
_ if i == 0 => assert_eq!((GuidEventRecord::TYPE, GuidEventRecord::REVISION), actual),
_ if i == 1 => {
assert_eq!((DynamicStringEventRecord::TYPE, DynamicStringEventRecord::REVISION), actual)
}
_ if i == 2 => {
assert_eq!((DualGuidStringEventRecord::TYPE, DualGuidStringEventRecord::REVISION), actual)
}
_ if i == 3 => assert_eq!((GuidQwordEventRecord::TYPE, GuidQwordEventRecord::REVISION), actual),
_ if i == 4 => {
assert_eq!((GuidQwordStringEventRecord::TYPE, GuidQwordStringEventRecord::REVISION), actual)
}
_ => unreachable!(),
}
}
assert_eq!(&273, fbpt.length());
}
#[test]
fn test_published_table_size() {
let mut fbpt = Fbpt::new();
let empty_size = fbpt.published_table_size();
let guid = patina::BinaryGuid::ZERO;
fbpt.add_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
fbpt.add_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
assert_eq!(empty_size + fbpt.perf_records().size(), fbpt.published_table_size());
}
#[test]
fn test_performance_table_well_written_in_memory() {
let buffer: &'static mut [u8] = alloc::boxed::Box::leak(alloc::vec![0u8; 1000].into_boxed_slice());
let address = buffer.as_ptr() as usize;
let mut fbpt = Fbpt::new();
let guid = patina::BinaryGuid::ZERO;
fbpt.add_record(GuidEventRecord::new(1, 0, 10, guid)).unwrap();
fbpt.add_record(DynamicStringEventRecord::new(1, 0, 10, guid, "test")).unwrap();
fbpt.publish_table(buffer).unwrap();
assert_eq!(address, fbpt.fbpt_address());
fbpt.add_record(DualGuidStringEventRecord::new(1, 0, 10, guid, guid, "test")).unwrap();
fbpt.add_record(GuidQwordEventRecord::new(1, 0, 10, guid, 64)).unwrap();
fbpt.add_record(GuidQwordStringEventRecord::new(1, 0, 10, guid, 64, "test")).unwrap();
let buffer = unsafe { slice::from_raw_parts(fbpt.fbpt_address() as *const u8, 1000) };
let mut offset = 0;
let signature = buffer.gread_with::<u32>(&mut offset, scroll::NATIVE).unwrap();
assert_eq!(Fbpt::SIGNATURE, signature);
let length = buffer.gread_with::<u32>(&mut offset, scroll::NATIVE).unwrap();
assert_eq!(fbpt.length(), &length);
let record_type = buffer.gread_with::<u16>(&mut offset, scroll::NATIVE).unwrap();
let record_length = buffer.gread_with::<u8>(&mut offset, scroll::NATIVE).unwrap();
let record_revision = buffer.gread_with::<u8>(&mut offset, scroll::NATIVE).unwrap();
assert_eq!(FirmwareBasicBootPerfDataRecord::TYPE, record_type);
assert_eq!(
PERFORMANCE_RECORD_HEADER_SIZE + FirmwareBasicBootPerfDataRecord::data_size(),
record_length as usize
);
assert_eq!(FirmwareBasicBootPerfDataRecord::REVISION, record_revision);
offset += FirmwareBasicBootPerfDataRecord::data_size();
assert_eq!(fbpt.perf_records().buffer().as_ptr() as usize, address + offset);
}
}