use core::convert::TryInto;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::slice::Chunks;
use crate::{
bitfield::{BitField, FlagType, Layout},
InfoType,
MalformedStructureError::{self, InvalidFormattedSectionLength},
RawStructure,
};
pub mod log_record_format;
pub use self::log_record_format::{EventLogType, VariableDataFormatType};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct SystemEventLog<'a> {
pub handle: u16,
pub log_area_length: u16,
pub log_header_start_offset: u16,
pub log_data_start_offset: u16,
pub access_method: AccessMethod,
pub log_status: LogStatus,
pub log_change_token: u32,
pub log_header_format: Option<LogHeaderFormat>,
pub supported_event_log_type_descriptors: Option<SupportedEventLogTypeDescriptors<'a>>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum AccessMethod {
IndexedIoOne8bitIndexOne8bitData { index: u8, data: u8 },
IndexedIoTwo8bitIndexOne8bitData { index: [u8; 2], data: u8 },
IndexedIoOne16bitIndexOne8bitData { index: u16, data: u8 },
MemoryMappedPhysicaAddress { physical_address: u32 },
GeneralPurposeNonVolatileData { gpnv_handle: u16 },
Available { method: u8, address: u32 },
OemSpecific { method: u8, address: u32 },
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct LogStatus(u8);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum LogHeaderFormat {
NoHeader,
LogHeaderType1,
Available(u8),
OemSpecific(u8),
}
#[derive(Clone, Debug)]
pub struct SupportedEventLogTypeDescriptors<'a>(Chunks<'a, u8>);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct EventLogTypeDescriptor {
pub log_type: EventLogType,
pub variable_data_format_type: VariableDataFormatType,
}
impl<'a> SystemEventLog<'a> {
pub(crate) fn try_from(structure: RawStructure<'a>) -> Result<Self, MalformedStructureError> {
let handle = structure.handle;
let number_of_supported_log_type_descriptors = structure.get::<u8>(0x15).ok();
let length_of_each_log_type_descriptor = structure.get::<u8>(0x16).ok();
let len_gt_2_1 = number_of_supported_log_type_descriptors
.and_then(|x| length_of_each_log_type_descriptor.map(|y| 0x17 + x as usize * y as usize));
match (
(structure.version.major, structure.version.minor),
structure.data.len() + 4,
) {
(v, l) if v == (2, 0) && l != 0x14 => Err(InvalidFormattedSectionLength(
InfoType::SystemEventLog,
handle,
"",
0x14,
)),
(v, l) if v >= (2, 1) && Some(l) != len_gt_2_1 => {
if let Some(len) = len_gt_2_1 {
Err(InvalidFormattedSectionLength(
InfoType::SystemEventLog,
handle,
"17h+(x*y) = ",
len as u8,
))
} else {
Err(InvalidFormattedSectionLength(
InfoType::SystemEventLog,
handle,
"minimum of ",
0,
))
}
}
_ => {
let access_method = {
let method = structure.get::<u8>(0x0A)?;
let address = structure.get::<u32>(0x10)?;
AccessMethod::new(method, address)
};
let supported_event_log_type_descriptors = (|| {
let number = number_of_supported_log_type_descriptors? as usize;
let length = length_of_each_log_type_descriptor? as usize;
let data = structure.get_slice(0x17, number * length)?;
Some(SupportedEventLogTypeDescriptors::new(data, length))
})();
Ok(Self {
handle,
log_area_length: structure.get::<u16>(0x04)?,
log_header_start_offset: structure.get::<u16>(0x06)?,
log_data_start_offset: structure.get::<u16>(0x08)?,
access_method,
log_status: structure.get::<u8>(0x0B)?.into(),
log_change_token: structure.get::<u32>(0x0C)?,
log_header_format: structure.get::<u8>(0x14).ok().map(Into::into),
supported_event_log_type_descriptors,
})
}
}
}
}
impl AccessMethod {
pub fn new(method: u8, address: u32) -> Self {
let [index_lsb, index_msb, data_lsb, _data_msb] = u32::to_le_bytes(address);
match method {
0x00 => Self::IndexedIoOne8bitIndexOne8bitData {
index: index_lsb,
data: data_lsb,
},
0x01 => Self::IndexedIoTwo8bitIndexOne8bitData {
index: [index_lsb, index_msb],
data: data_lsb,
},
0x02 => Self::IndexedIoOne16bitIndexOne8bitData {
index: u16::from_le_bytes([index_lsb, index_msb]),
data: data_lsb,
},
0x03 => Self::MemoryMappedPhysicaAddress {
physical_address: address,
},
0x04 => Self::GeneralPurposeNonVolatileData {
gpnv_handle: u16::from_le_bytes([index_lsb, index_msb]),
},
method @ 0x80..=0xFF => Self::OemSpecific { method, address },
method => Self::Available { method, address },
}
}
pub fn address(&self) -> u32 {
match self {
Self::IndexedIoOne8bitIndexOne8bitData { index, data } => u32::from_le_bytes([*index, 0, *data, 0]),
Self::IndexedIoTwo8bitIndexOne8bitData { index, data } => {
u32::from_le_bytes([index[0], index[1], *data, 0])
}
Self::IndexedIoOne16bitIndexOne8bitData { index, data } => {
let index = u16::to_le_bytes(*index);
u32::from_le_bytes([index[0], index[1], *data, 0])
}
Self::MemoryMappedPhysicaAddress { physical_address } => *physical_address,
Self::GeneralPurposeNonVolatileData { gpnv_handle } => *gpnv_handle as u32,
Self::OemSpecific { address, .. } => *address,
Self::Available { address, .. } => *address,
}
}
}
impl fmt::Display for AccessMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (f.alternate(), self) {
(false, Self::IndexedIoOne8bitIndexOne8bitData { .. }) => {
write!(f, "Indexed I/O, one 8-bit index port, one 8-bit data port")
}
(false, Self::IndexedIoTwo8bitIndexOne8bitData { .. }) => {
write!(f, "Indexed I/O, two 8-bit index ports, one 8-bit data port")
}
(false, Self::IndexedIoOne16bitIndexOne8bitData { .. }) => {
write!(f, "Indexed I/O, one 16-bit index port, one 8-bit data port")
}
(false, Self::MemoryMappedPhysicaAddress { .. }) => write!(f, "Memory-mapped physical 32-bit address"),
(false, Self::GeneralPurposeNonVolatileData { .. }) => {
write!(f, "General-purpose non-volatile data functions")
}
(false, Self::OemSpecific { method, .. }) => write!(f, "OEM-specific: {method}"),
(false, Self::Available { method, .. }) => write!(f, "Available: {method}"),
(true, Self::IndexedIoOne8bitIndexOne8bitData { index, data }) => write!(
f,
"Indexed I/O, one 8-bit index port, one 8-bit data port: Index 0x{index:02X}, Data 0x{data:02X}"
),
(true, Self::IndexedIoTwo8bitIndexOne8bitData { index, data }) => write!(
f,
"Indexed I/O, two 8-bit index ports, one 8-bit data port: Index {index:X?}, Data 0x{data:02X}"
),
(true, Self::IndexedIoOne16bitIndexOne8bitData { index, data }) => write!(
f,
"Indexed I/O, one 16-bit index port, one 8-bit data port: Index 0x{index:04X}, Data 0x{data:02X}"
),
(true, Self::MemoryMappedPhysicaAddress { physical_address }) => {
write!(f, "Memory-mapped physical 32-bit address: 0x{physical_address:08X}")
}
(true, Self::GeneralPurposeNonVolatileData { gpnv_handle }) => write!(
f,
"General-Purpose NonVolatile Data functions, handle 0x{gpnv_handle:04X}"
),
(true, Self::OemSpecific { method, address }) => {
write!(f, "BIOS Vendor/OEM-specific: Method {method}, Address 0x{address:08X}")
}
(true, Self::Available { method, address }) => {
write!(f, "Available: Method {method}, Address 0x{address:08X}")
}
}
}
}
impl BitField<'_> for LogStatus {
type Size = u8;
fn value(&self) -> Self::Size {
self.0
}
layout!(
length = 8;
"Valid" "Log area valid",
"Full" "Log area full",
"Reserved": 6,
);
}
impl From<u8> for LogStatus {
fn from(byte: u8) -> Self {
Self(byte)
}
}
impl From<u8> for LogHeaderFormat {
fn from(byte: u8) -> Self {
match byte {
0x00 => Self::NoHeader,
0x01 => Self::LogHeaderType1,
v @ 0x80..=0xFF => Self::OemSpecific(v),
v => Self::Available(v),
}
}
}
impl fmt::Display for LogHeaderFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (f.alternate(), self) {
(_, Self::NoHeader) => write!(f, "No Header"),
(true, Self::LogHeaderType1) => write!(f, "Type 1 log header"),
(false, Self::LogHeaderType1) => write!(f, "Type 1"),
(true, Self::OemSpecific(v)) => write!(f, "BIOS vendor or OEM-specific format: {v}"),
(false, Self::OemSpecific(_)) => write!(f, "OEM-specific"),
(_, Self::Available(v)) => write!(f, "Available: {v}"),
}
}
}
impl<'a> SupportedEventLogTypeDescriptors<'a> {
fn new(data: &'a [u8], size: usize) -> Self {
Self(data.chunks(size))
}
}
impl PartialEq for SupportedEventLogTypeDescriptors<'_> {
fn eq(&self, other: &Self) -> bool {
self.0.clone().eq(other.0.clone())
}
}
impl Eq for SupportedEventLogTypeDescriptors<'_> {}
impl Hash for SupportedEventLogTypeDescriptors<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.clone().for_each(|c| c.hash(state));
}
}
impl Iterator for SupportedEventLogTypeDescriptors<'_> {
type Item = EventLogTypeDescriptor;
fn next(&mut self) -> Option<Self::Item> {
let next = self.0.next()?;
next.try_into().ok().map(|a: [u8; 2]| EventLogTypeDescriptor {
log_type: a[0].into(),
variable_data_format_type: a[1].into(),
})
}
}
impl From<[u8; 2]> for EventLogTypeDescriptor {
fn from(a: [u8; 2]) -> Self {
Self {
log_type: a[0].into(),
variable_data_format_type: a[1].into(),
}
}
}
impl From<EventLogTypeDescriptor> for [u8; 2] {
fn from(d: EventLogTypeDescriptor) -> Self {
[d.log_type.into(), d.variable_data_format_type.into()]
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq as pretty_assert_eq;
use std::prelude::v1::*;
#[test]
fn access_method() {
use super::AccessMethod;
let address = u32::from_le_bytes([0x78, 0x56, 0x34, 0x12]);
let data = &[
(
0,
"Indexed I/O, one 8-bit index port, one 8-bit data port: Index 0x78, Data 0x34",
),
(
1,
"Indexed I/O, two 8-bit index ports, one 8-bit data port: Index [78, 56], Data 0x34",
),
(
2,
"Indexed I/O, one 16-bit index port, one 8-bit data port: Index 0x5678, Data 0x34",
),
(3, "Memory-mapped physical 32-bit address: 0x12345678"),
(4, "General-Purpose NonVolatile Data functions, handle 0x5678"),
(5, "Available: Method 5, Address 0x12345678"),
(0x80, "BIOS Vendor/OEM-specific: Method 128, Address 0x12345678"),
];
for (m, s) in data {
pretty_assert_eq!(*s, format!("{:#}", AccessMethod::new(*m, address)));
}
}
#[test]
fn log_status() {
use super::LogStatus;
use crate::bitfield::BitField;
let byte: u8 = 0b111;
let ls: LogStatus = byte.into();
let sample = vec!["Log area valid", "Log area full"];
pretty_assert_eq!(sample, ls.significants().map(|v| format!("{v:#}")).collect::<Vec<_>>());
}
#[test]
fn log_header_format() {
use super::LogHeaderFormat;
let data = &[
(0u8, "No Header"),
(1, "Type 1 log header"),
(2, "Available: 2"),
(0xFF, "BIOS vendor or OEM-specific format: 255"),
];
for (v, s) in data {
pretty_assert_eq!(*s, format!("{:#}", LogHeaderFormat::from(*v)));
}
}
#[test]
fn supported_event_log_type_descriptors() {
use super::{
EventLogType as T, EventLogTypeDescriptor as Desc, SupportedEventLogTypeDescriptors,
VariableDataFormatType as D,
};
let data = &[
0x02, 0x00, 0x04, 0x01, 0x08, 0x02, 0x16, 0x03, 0x66, 0x00, 0xEE, 0x00, 0xFF, 0x00,
];
let sample = vec![
Desc {
log_type: T::MultiBitEccMemoryError,
variable_data_format_type: D::None,
},
Desc {
log_type: T::BusTimeOut,
variable_data_format_type: D::Handle { handle: 0 },
},
Desc {
log_type: T::PostError,
variable_data_format_type: D::MultipleEvent { counter: 0 },
},
Desc {
log_type: T::LogAreaReset,
variable_data_format_type: D::MultipleEventHandle { handle: 0, counter: 0 },
},
Desc {
log_type: T::Unused(0x66),
variable_data_format_type: D::None,
},
Desc {
log_type: T::Available(0xEE),
variable_data_format_type: D::None,
},
Desc {
log_type: T::EndOfLog,
variable_data_format_type: D::None,
},
];
let result = SupportedEventLogTypeDescriptors::new(data, 2);
pretty_assert_eq!(sample, result.collect::<Vec<_>>());
}
#[test]
fn system_event_log() {
use super::EventLogType as T;
use super::VariableDataFormatType as D;
use super::*;
use crate::{bitfield::Position, InfoType, RawStructure};
let length = 77 - 4;
let (data, strings) =
include_bytes!("../../../tests/data/02daadcd/entries/15-0/bin")[4..].split_at(length as usize);
let structure = RawStructure {
version: (2, 7).into(),
info: InfoType::SystemEventLog,
length,
handle: 0x0036,
data,
strings,
};
let result = SystemEventLog::try_from(structure).unwrap();
let access_method = AccessMethod::MemoryMappedPhysicaAddress {
physical_address: 0xFFC40000,
};
pretty_assert_eq!(access_method, result.access_method, "AccessMethod");
let log_status = [Position(0)].iter().collect::<u8>().into();
pretty_assert_eq!(log_status, result.log_status, "LogStatus");
let seltd_length = result.supported_event_log_type_descriptors.clone().unwrap().count();
pretty_assert_eq!(27, seltd_length, "Supported Log Type Descriptors count");
let seltd_sample = [
(T::SingleBitEccMemoryError, D::None),
(T::MultiBitEccMemoryError, D::None),
(T::ParityMemoryError, D::None),
(T::BusTimeOut, D::None),
(T::IoChannelCheck, D::None),
(T::SoftwareNmi, D::None),
(T::PostMemoryResize, D::None),
(T::PostError, D::PostResults(0.into())),
(T::PciParityError, D::None),
(T::PciSystemError, D::None),
(T::CpuFailure, D::None),
(T::EisaFailSafeTimerTimeOut, D::None),
(T::CorrectableMemoryLogDisabled, D::None),
(T::LoggingDisabledForSpecificEventType, D::None),
(T::SystemLimitExceeded, D::None),
(T::AsynchronousHardwareTimerExpired, D::None),
(T::SystemConfigurationInformation, D::None),
(T::HardDiskInformation, D::None),
(T::SystemReconfigured, D::None),
(T::UncorrectableCpuComplexError, D::None),
(T::LogAreaReset, D::None),
(T::SystemBoot, D::None),
(T::EndOfLog, D::None),
(T::Available(0xB0), D::OemAssigned(0xB0)),
(T::Available(0xB1), D::OemAssigned(0xB1)),
(T::Available(0xE0), D::OemAssigned(0xE0)),
(T::Available(0xE1), D::OemAssigned(0xE1)),
]
.iter()
.map(|(t, d)| EventLogTypeDescriptor {
log_type: *t,
variable_data_format_type: *d,
})
.collect::<Vec<_>>();
let seltd_result = result
.supported_event_log_type_descriptors
.clone()
.unwrap()
.collect::<Vec<_>>();
pretty_assert_eq!(seltd_sample, seltd_result, "SupportedEventLogTypeDescriptors");
let sample_bytes = seltd_sample.iter().fold(Vec::new(), |mut vec: Vec<u8>, eltd| {
vec.push(eltd.log_type.into());
vec.push(eltd.variable_data_format_type.into());
vec
});
let sample = SystemEventLog {
handle: 0x0036,
log_area_length: 16383,
log_header_start_offset: 0x0000,
log_data_start_offset: 0x0010,
access_method,
log_status,
log_change_token: 0x00000001,
log_header_format: Some(LogHeaderFormat::LogHeaderType1),
supported_event_log_type_descriptors: Some(SupportedEventLogTypeDescriptors::new(&sample_bytes, 2)),
};
pretty_assert_eq!(sample, result, "SystemEventLog");
}
}