Skip to main content

intel_crashlog/
source.rs

1// Copyright (C) 2026 Intel Corporation
2// SPDX-License-Identifier: MIT
3
4//! Crash Log sources and their capabilities
5//!
6//! This module provides abstractions for discovering, querying, and extracting Crash Log data
7//! from various platform sources. Each source has different capabilities such as extraction,
8//! on-demand triggering, and enable/disable control.
9//!
10//! # Supported Sources
11//!
12//! - **ACPI BERT** (`acpi`) - Boot Error Record Table for firmware-reported errors
13//! - **Intel PMT** (`pmt:<device>`) - Platform Monitoring Technology devices with Crash Log regions
14//! - **Event Log** (`evt`) - Operating system event logs (Windows)
15//!
16//! # Examples
17//!
18//! Discovering available sources:
19//!
20//! ```
21//! use intel_crashlog::prelude::*;
22//!
23//! let sources = CrashLogSource::discover();
24//! for source in sources {
25//!     println!("{}: {}", source, source.description());
26//!     println!("Capabilities: {:?}", source.capabilities());
27//! }
28//! ```
29mod acpi;
30mod capability;
31mod event_log;
32mod pmt;
33
34use crate::CrashLog;
35use crate::error::Error;
36use acpi::Acpi;
37#[cfg(not(feature = "std"))]
38use alloc::{fmt, str::FromStr, string::String, string::ToString, vec, vec::Vec};
39use event_log::EventLog;
40use pmt::{Pmt, PmtDeviceId};
41#[cfg(feature = "std")]
42use std::{fmt, str::FromStr};
43
44pub use capability::{Capabilities, Capability};
45
46/// Represents a source from which Crash Log data can be extracted
47///
48/// Each source support different type of capabilities, which are represented as
49/// [Capabilities]
50#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
51pub enum CrashLogSource {
52    /// ACPI BERT table
53    Acpi,
54    /// Intel PMT device that exposes a Crash Log region
55    PmtDevice(PmtDeviceId),
56    /// OS Event Log
57    EventLog,
58}
59
60impl fmt::Display for CrashLogSource {
61    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62        match self {
63            Self::Acpi => write!(f, "acpi"),
64            Self::PmtDevice(dev) => write!(f, "pmt:{}", dev),
65            Self::EventLog => write!(f, "evt"),
66        }
67    }
68}
69
70/// Error type returned when parsing a [`CrashLogSource`] from a string fails
71///
72/// This error is produced by the [`FromStr`] implementation for [`CrashLogSource`]
73/// when the input string cannot be parsed into a valid Crash Log source.
74#[derive(Debug, PartialEq)]
75pub enum ParseCrashLogSourceError {
76    /// The source name is not recognized
77    ///
78    /// Valid source names are: `acpi`, `pmt`, and `evt`
79    InvalidSource,
80    /// The parameter provided for the source is invalid
81    ///
82    /// This occurs when:
83    /// - A parameter is provided for sources that don't accept parameters (`acpi`, `evt`)
84    /// - The parameter format is invalid for PMT sources (must be either a device name
85    ///   like `crashlog0` or a PCI BDF address like `0000:00:1f.5`)
86    InvalidParameter,
87}
88
89impl fmt::Display for ParseCrashLogSourceError {
90    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
91        match self {
92            Self::InvalidSource => write!(f, "Invalid source"),
93            Self::InvalidParameter => write!(f, "Invalid parameter"),
94        }
95    }
96}
97
98#[cfg(feature = "std")]
99impl std::error::Error for ParseCrashLogSourceError {}
100
101impl FromStr for CrashLogSource {
102    type Err = ParseCrashLogSourceError;
103
104    fn from_str(s: &str) -> Result<Self, Self::Err> {
105        let (source, parameter) = s.split_once(":").unwrap_or((s, ""));
106
107        match source {
108            "acpi" => {
109                if parameter.is_empty() {
110                    Ok(Self::Acpi)
111                } else {
112                    Err(Self::Err::InvalidParameter)
113                }
114            }
115            "pmt" => {
116                if let Ok(dev) = parameter.parse() {
117                    Ok(Self::PmtDevice(dev))
118                } else {
119                    Err(Self::Err::InvalidParameter)
120                }
121            }
122            "evt" => {
123                if parameter.is_empty() {
124                    Ok(Self::EventLog)
125                } else {
126                    Err(Self::Err::InvalidParameter)
127                }
128            }
129            _ => Err(Self::Err::InvalidSource),
130        }
131    }
132}
133
134impl CrashLogSource {
135    /// Returns all the Crash Log sources that are available in the platform
136    pub fn discover() -> Vec<Self> {
137        let mut sources = Vec::new();
138
139        if Acpi::default().is_available() {
140            sources.push(Self::Acpi);
141        }
142
143        if EventLog::default().is_available() {
144            sources.push(Self::EventLog);
145        }
146
147        let pmt_devices: Vec<CrashLogSource> = Pmt::default()
148            .discover()
149            .into_iter()
150            .map(Self::PmtDevice)
151            .collect();
152
153        sources.extend(pmt_devices);
154
155        sources
156    }
157
158    /// Returns the Crash Log extracted from the platform using the current Crash Log source
159    #[cfg(feature = "extraction")]
160    pub fn extract(&self) -> Result<Vec<CrashLog>, Error> {
161        let mut crashlogs = match self {
162            Self::Acpi => Acpi::default().extract().map(|crashlog| vec![crashlog]),
163            Self::PmtDevice(dev) => Pmt::default().extract(dev),
164            Self::EventLog => EventLog::default().extract(),
165        };
166
167        if let Ok(ref mut crashlogs) = crashlogs {
168            for crashlog in crashlogs.iter_mut() {
169                crashlog.metadata.source = Some(self.clone());
170            }
171        }
172
173        crashlogs
174    }
175
176    /// Triggers an on-demand Crash Log collection on this source
177    #[cfg(feature = "control_commands")]
178    pub fn trigger(&self) -> Result<(), Error> {
179        match self {
180            Self::PmtDevice(dev) => Pmt::default().trigger(dev),
181            _ => Err(Error::Unsupported),
182        }
183    }
184
185    /// Clears the Crash Log storage on this source
186    #[cfg(feature = "control_commands")]
187    pub fn clear(&self) -> Result<(), Error> {
188        match self {
189            Self::PmtDevice(dev) => Pmt::default().clear(dev),
190            _ => Err(Error::Unsupported),
191        }
192    }
193
194    /// Enable the Crash Log collection on this source
195    #[cfg(feature = "control_commands")]
196    pub fn enable(&self) -> Result<(), Error> {
197        match self {
198            Self::PmtDevice(dev) => Pmt::default().enable_disable(dev, true),
199            _ => Err(Error::Unsupported),
200        }
201    }
202
203    /// Disable the Crash Log collection on this source
204    #[cfg(feature = "control_commands")]
205    pub fn disable(&self) -> Result<(), Error> {
206        match self {
207            Self::PmtDevice(dev) => Pmt::default().enable_disable(dev, false),
208            _ => Err(Error::Unsupported),
209        }
210    }
211
212    /// Returns a human readable description of the Crash Log source
213    pub fn description(&self) -> String {
214        match &self {
215            Self::Acpi => "ACPI BERT".to_string(),
216            Self::EventLog => "Windows Event Log".to_string(),
217            Self::PmtDevice(dev) => Pmt::default().description(dev),
218        }
219    }
220
221    /// Returns all the capabilities of the Crash Log Source
222    pub fn capabilities(&self) -> Capabilities {
223        match self {
224            Self::Acpi => Capabilities::from([Capability::Extract]),
225            Self::EventLog => Capabilities::from([Capability::Extract]),
226            Self::PmtDevice(dev) => Pmt::default().capabilities(dev),
227        }
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::pmt::PciBdf;
234    use super::*;
235
236    #[test]
237    fn parse() {
238        assert_eq!("acpi".parse(), Ok(CrashLogSource::Acpi));
239        assert_eq!(
240            "pmt:crashlog42".parse(),
241            Ok(CrashLogSource::PmtDevice(PmtDeviceId::Name(
242                "crashlog42".to_string()
243            )))
244        );
245        assert_eq!(
246            "pmt:1111:22:33.4".parse(),
247            Ok(CrashLogSource::PmtDevice(PmtDeviceId::Bdf(PciBdf::new(
248                0x1111, 0x22, 0x33, 0x4
249            ))))
250        );
251        assert_eq!("evt".parse(), Ok(CrashLogSource::EventLog));
252        assert_eq!(
253            "foo".parse::<CrashLogSource>(),
254            Err(ParseCrashLogSourceError::InvalidSource)
255        );
256        assert_eq!(
257            "acpi:foo".parse::<CrashLogSource>(),
258            Err(ParseCrashLogSourceError::InvalidParameter)
259        );
260    }
261}