intel_crashlog/source/pmt/
sysfs.rs1use super::PmtDeviceId;
5use super::bdf::PciBdf;
6use crate::CrashLog;
7use crate::error::Error;
8use crate::region::Region;
9use crate::source::{Capabilities, Capability};
10use std::collections::BTreeSet;
11use std::io::Write;
12use std::path::{Path, PathBuf};
13
14pub(super) struct PmtSysFs {
15 root: PathBuf,
16}
17
18pub(super) struct PmtSysFsEndpoint {
19 path: PathBuf,
20}
21
22impl Default for PmtSysFs {
23 fn default() -> Self {
24 Self::new(Path::new("/"))
25 }
26}
27
28impl PmtSysFs {
29 pub fn new(path: &Path) -> Self {
30 Self {
31 root: path.to_owned(),
32 }
33 }
34
35 fn class_path(&self) -> PathBuf {
36 let mut path = self.root.clone();
37 path.push("sys");
38 path.push("class");
39 path.push("intel_pmt");
40 path
41 }
42
43 fn pci_dev_path(&self) -> PathBuf {
44 let mut path = self.root.clone();
45 path.push("sys");
46 path.push("bus");
47 path.push("pci");
48 path.push("devices");
49 path
50 }
51
52 fn discover_from_class(&self) -> Vec<PmtDeviceId> {
53 let path = self.class_path();
54
55 std::fs::read_dir(&path)
56 .inspect_err(|err| log::warn!("Cannot read {}: {err}", path.display()))
57 .ok()
58 .map(|read_dir| {
59 read_dir
60 .filter_map(|entry| entry.ok())
61 .filter_map(|entry| {
62 let name = entry.file_name();
63 let entry_name = name.to_str().unwrap_or_default();
64
65 if entry_name.trim_end_matches(char::is_numeric) == "crashlog" {
66 Some(PmtDeviceId::Name(entry_name.to_string()))
67 } else {
68 None
69 }
70 })
71 .collect()
72 })
73 .unwrap_or_default()
74 }
75
76 fn has_crashlog_vsec(path: &Path) -> bool {
77 match std::fs::read_dir(path) {
78 Err(err) => log::warn!("Cannot read {}: {err}", path.display()),
79 Ok(entries) => {
80 for entry in entries {
81 let Ok(entry) = entry else { continue };
82 let filename = entry.file_name();
83 let Some(filename) = filename.to_str() else {
84 continue;
85 };
86 if filename.trim_end_matches(char::is_numeric) == "intel_vsec.crashlog." {
87 return true;
88 }
89 }
90 }
91 }
92
93 false
94 }
95
96 fn discover_from_pci(&self) -> Vec<PmtDeviceId> {
97 let path = self.pci_dev_path();
98 let mut devices = BTreeSet::default();
99
100 match std::fs::read_dir(&path) {
101 Err(err) => log::warn!("Cannot read {}: {err}", path.display()),
102 Ok(entries) => {
103 for entry in entries {
104 let Ok(entry) = entry else {
105 continue;
106 };
107 let filename = entry.file_name();
108 let Some(filename) = filename.to_str() else {
109 continue;
110 };
111 let Ok(bdf) = filename.parse() else {
112 continue;
113 };
114
115 if Self::has_crashlog_vsec(&entry.path()) {
116 devices.insert(PmtDeviceId::Bdf(bdf));
117 }
118 }
119 }
120 }
121
122 devices.into_iter().collect()
123 }
124
125 pub fn discover(&self) -> Vec<PmtDeviceId> {
126 let mut devices = self.discover_from_class();
127 devices.extend(self.discover_from_pci());
128 devices
129 }
130
131 pub fn get_all_endpoints(&self) -> Vec<PmtSysFsEndpoint> {
132 self.discover()
133 .into_iter()
134 .flat_map(|devid| self.get_endpoints(&devid))
135 .collect()
136 }
137
138 fn get_endpoints_from_vsec(path: &Path) -> Vec<PmtSysFsEndpoint> {
139 let mut pmt_path = path.to_owned();
140 pmt_path.push("intel_pmt");
141 let mut endpoints = Vec::default();
142
143 match std::fs::read_dir(&pmt_path) {
144 Err(err) => log::warn!("Cannot read {}: {err}", path.display()),
145 Ok(entries) => {
146 for entry in entries {
147 let Ok(entry) = entry else {
148 continue;
149 };
150 let filename = entry.file_name();
151 let Some(filename) = filename.to_str() else {
152 continue;
153 };
154 if filename.trim_end_matches(char::is_numeric) == "crashlog"
155 && let Some(endpoint) = PmtSysFsEndpoint::new(&entry.path())
156 {
157 endpoints.push(endpoint);
158 }
159 }
160 }
161 }
162
163 endpoints
164 }
165
166 fn get_endpoints_from_bdf(&self, bdf: &PciBdf) -> Vec<PmtSysFsEndpoint> {
167 let mut endpoints = Vec::default();
168 let mut path = self.pci_dev_path();
169 path.push(bdf.to_string());
170
171 match std::fs::read_dir(&path) {
172 Err(err) => log::error!("Cannot read {}: {err}", path.display()),
173 Ok(entries) => {
174 for entry in entries {
175 let Ok(entry) = entry else {
176 continue;
177 };
178 let filename = entry.file_name();
179 let Some(filename) = filename.to_str() else {
180 continue;
181 };
182 if filename.trim_end_matches(char::is_numeric) == "intel_vsec.crashlog." {
183 endpoints.extend(Self::get_endpoints_from_vsec(&entry.path()));
184 }
185 }
186 }
187 }
188
189 endpoints
190 }
191
192 pub fn get_endpoints(&self, id: &PmtDeviceId) -> Vec<PmtSysFsEndpoint> {
193 match id {
194 PmtDeviceId::Name(name) => {
195 let mut path = self.class_path();
196 path.push(name);
197
198 let mut endpoints = Vec::new();
199 if let Some(endpoint) = PmtSysFsEndpoint::new(&path) {
200 endpoints.push(endpoint);
201 }
202 endpoints
203 }
204 PmtDeviceId::Bdf(bdf) => self.get_endpoints_from_bdf(bdf),
205 }
206 }
207
208 pub fn extract(&self, dev: &PmtDeviceId) -> Result<Vec<CrashLog>, Error> {
209 let mut crashlogs = Vec::new();
210
211 for endpoint in self.get_endpoints(dev) {
212 match endpoint.extract() {
213 Ok(crashlog) => crashlogs.push(crashlog),
214 Err(Error::EmptyRegion) => (),
215 Err(err) => return Err(err),
216 }
217 }
218
219 Ok(crashlogs)
220 }
221
222 pub fn capabilities(&self, dev: &PmtDeviceId) -> Capabilities {
223 let mut capabilities = Capabilities::new();
224
225 for endpoint in self.get_endpoints(dev) {
226 capabilities.append(&mut endpoint.capabilities());
227 }
228
229 capabilities
230 }
231}
232
233impl PmtSysFsEndpoint {
234 pub fn new(path: &Path) -> Option<Self> {
235 if path.is_dir() {
236 Some(Self {
237 path: path.to_owned(),
238 })
239 } else {
240 log::error!("{} is not a valid PMT Crash Log endpoint", path.display());
241 None
242 }
243 }
244
245 #[cfg(feature = "control_commands")]
246 pub fn trigger(&self) -> Result<(), Error> {
247 self.write_command("trigger", b"1")
248 }
249
250 #[cfg(feature = "control_commands")]
251 pub fn clear(&self) -> Result<(), Error> {
252 self.write_command("clear", b"1")
253 }
254
255 #[cfg(feature = "control_commands")]
256 pub fn enable_disable(&self, enable: bool) -> Result<(), Error> {
257 self.write_command("enable", if enable { b"1" } else { b"0" })
258 }
259
260 #[cfg(feature = "control_commands")]
261 fn write_command(&self, entry: &str, value: &[u8]) -> Result<(), Error> {
262 let path = self.path.join(entry);
263
264 let mut file = std::fs::OpenOptions::new()
265 .write(true)
266 .truncate(true)
267 .open(&path)
268 .map_err(Error::IOError)
269 .inspect_err(|err| log::warn!("{}: {err}", path.display()))?;
270
271 file.write_all(value)
272 .map_err(Error::IOError)
273 .inspect_err(|err| log::warn!("Failed to write to {}: {err}", path.display()))?;
274
275 Ok(())
276 }
277
278 pub fn capabilities(&self) -> Capabilities {
279 let mut capabilities: Capabilities = Capabilities::new();
280
281 if self.path.join("crashlog").exists() {
282 capabilities.insert(Capability::Extract);
283 }
284
285 if self.path.join("trigger").exists() {
286 capabilities.insert(Capability::Trigger);
287 }
288
289 if self.path.join("enable").exists() {
290 capabilities.insert(Capability::EnableDisable);
291 }
292
293 if self.path.join("clear").exists() {
294 capabilities.insert(Capability::Clear);
295 }
296
297 capabilities
298 }
299
300 pub fn extract(&self) -> Result<CrashLog, Error> {
301 CrashLog::from_regions(vec![
302 std::fs::read(self.path.join("crashlog"))
303 .map_err(Error::IOError)
304 .and_then(|region| Region::from_slice(®ion))
305 .inspect(|_| log::info!("Extracted valid record from {}", self.path.display()))
306 .inspect_err(|err| log::error!("{}: {err}", self.path.display()))?,
307 ])
308 }
309}
310
311impl CrashLog {
312 pub fn from_pmt_sysfs() -> Result<Self, Error> {
314 let regions: Vec<Region> = PmtSysFs::default()
315 .get_all_endpoints()
316 .into_iter()
317 .filter_map(|dev| dev.extract().ok())
318 .flat_map(|crashlog| crashlog.regions)
319 .collect();
320
321 CrashLog::from_regions(regions)
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use tempfile;
329
330 #[test]
331 fn discovery() {
332 let root = tempfile::tempdir().unwrap();
333
334 let sysfs = PmtSysFs::new(root.path());
335 assert!(sysfs.discover().is_empty());
336
337 let mut class_path = root.path().to_owned();
338 class_path.push("sys");
339 class_path.push("class");
340 class_path.push("intel_pmt");
341 std::fs::create_dir_all(&class_path).unwrap();
342
343 for dev in 0..2 {
344 let mut dev_path = class_path.clone();
345 dev_path.push(format!("crashlog{}", dev));
346 std::fs::create_dir(dev_path).unwrap();
347 }
348
349 let mut dummy_dev_path = class_path.clone();
350 dummy_dev_path.push("foo");
351 std::fs::create_dir(dummy_dev_path).unwrap();
352
353 assert_eq!(sysfs.discover_from_class().len(), 2);
354 }
355
356 #[test]
357 fn discovery_pci() {
358 let root = tempfile::tempdir().unwrap();
359
360 let sysfs = PmtSysFs::new(root.path());
361 assert!(sysfs.discover().is_empty());
362
363 let mut pci_root_path = root.path().to_owned();
364 pci_root_path.push("sys");
365 pci_root_path.push("bus");
366 pci_root_path.push("pci");
367 pci_root_path.push("devices");
368 std::fs::create_dir_all(&pci_root_path).unwrap();
369
370 let bdfs = vec!["0000:00:1f.5", "0000:00:0e.0"];
372
373 for bdf in &bdfs {
374 let mut bdf_dir = pci_root_path.clone();
375 bdf_dir.push(bdf);
376 std::fs::create_dir(&bdf_dir).unwrap();
377
378 let mut vsec_marker = bdf_dir.clone();
379 vsec_marker.push("intel_vsec.crashlog.42");
380 std::fs::create_dir(&vsec_marker).unwrap();
381
382 let mut vsec_marker = bdf_dir.clone();
383 vsec_marker.push("intel_vsec.crashlog.1337");
384 std::fs::create_dir(&vsec_marker).unwrap();
385 }
386
387 let devices = sysfs.discover();
388 assert_eq!(devices.len(), 2);
389
390 assert!(devices.contains(&PmtDeviceId::Bdf(PciBdf::new(0, 0, 0x1f, 5))));
391 assert!(devices.contains(&PmtDeviceId::Bdf(PciBdf::new(0, 0, 0x0e, 0))));
392 }
393
394 #[test]
395 fn extract() {
396 let root = tempfile::tempdir().unwrap();
397
398 let sysfs = PmtSysFs::new(root.path());
399 assert!(sysfs.discover().is_empty());
400
401 let mut dev_path = root.path().to_owned();
402 dev_path.push("sys");
403 dev_path.push("class");
404 dev_path.push("intel_pmt");
405 dev_path.push("crashlog0");
406 std::fs::create_dir_all(&dev_path).unwrap();
407
408 let data = std::fs::read("tests/samples/dummy_crashlog_agent_rev1.crashlog").unwrap();
409 let crashlog = CrashLog::from_slice(&data).unwrap();
410
411 let mut path = dev_path.to_owned();
412 path.push("crashlog");
413 std::fs::write(path, crashlog.regions[0].to_bytes()).unwrap();
414
415 let devices = sysfs.discover();
416 let dev = sysfs.get_endpoints(&devices[0]);
417
418 let extracted_crashlog = dev[0].extract().unwrap();
419 assert_eq!(crashlog.to_bytes(), extracted_crashlog.to_bytes());
420 }
421
422 #[test]
423 fn trigger() {
424 let root = tempfile::tempdir().unwrap();
425
426 let dev = PmtSysFsEndpoint::new(root.path()).unwrap();
427 assert!(matches!(dev.trigger(), Err(Error::IOError(_))));
428
429 let mut path = root.path().to_owned();
430 path.push("trigger");
431 std::fs::write(&path, b"0").unwrap();
432
433 dev.trigger().unwrap();
434 assert_eq!(&std::fs::read_to_string(&path).unwrap(), "1");
435 }
436
437 #[test]
438 fn enable_disable() {
439 let root = tempfile::tempdir().unwrap();
440
441 let dev = PmtSysFsEndpoint::new(root.path()).unwrap();
442 assert!(matches!(dev.enable_disable(true), Err(Error::IOError(_))));
443
444 let mut path = root.path().to_owned();
445 path.push("enable");
446 std::fs::write(&path, b"0").unwrap();
447
448 dev.enable_disable(true).unwrap();
449 assert_eq!(&std::fs::read_to_string(&path).unwrap(), "1");
450
451 dev.enable_disable(false).unwrap();
452 assert_eq!(&std::fs::read_to_string(&path).unwrap(), "0");
453 }
454
455 #[test]
456 fn capabilities() {
457 let root = tempfile::tempdir().unwrap();
458
459 let sysfs = PmtSysFs::new(root.path());
460 assert!(sysfs.discover().is_empty());
461
462 let mut dev_path = root.path().to_owned();
463 dev_path.push("sys");
464 dev_path.push("class");
465 dev_path.push("intel_pmt");
466 dev_path.push("crashlog0");
467 std::fs::create_dir_all(&dev_path).unwrap();
468
469 let mut cl_path = dev_path.to_owned();
470 cl_path.push("crashlog");
471 std::fs::create_dir(&cl_path).unwrap();
472
473 let mut trigger_path = dev_path.to_owned();
474 trigger_path.push("trigger");
475 std::fs::create_dir(&trigger_path).unwrap();
476
477 let devices = sysfs.discover();
478 let dev = sysfs.get_endpoints(&devices[0]);
479 let dev_capabilities = dev[0].capabilities();
480
481 assert_eq!(dev_capabilities.len(), 2);
482 assert!(dev_capabilities.contains(&Capability::Extract));
483 assert!(dev_capabilities.contains(&Capability::Trigger));
484 }
485
486 #[test]
487 fn control_commands() {
488 let root = tempfile::tempdir().unwrap();
489
490 let sysfs = PmtSysFs::new(root.path());
491 assert!(sysfs.discover().is_empty());
492
493 let mut dev_path = root.path().to_owned();
494 dev_path.push("sys");
495 dev_path.push("class");
496 dev_path.push("intel_pmt");
497 dev_path.push("crashlog0");
498 std::fs::create_dir_all(&dev_path).unwrap();
499
500 let mut enable_path = dev_path.to_owned();
501 enable_path.push("enable");
502 std::fs::write(&enable_path, b"0").unwrap();
503
504 let mut trigger_path = dev_path.to_owned();
505 trigger_path.push("trigger");
506 std::fs::write(&trigger_path, b"0").unwrap();
507
508 let devices = sysfs.discover();
509 let dev = sysfs.get_endpoints(&devices[0]);
510
511 dev[0].trigger().unwrap();
512 assert_eq!(&std::fs::read_to_string(&trigger_path).unwrap(), "1");
513
514 dev[0].enable_disable(true).unwrap();
515 assert_eq!(&std::fs::read_to_string(&enable_path).unwrap(), "1");
516
517 dev[0].enable_disable(false).unwrap();
518 assert_eq!(&std::fs::read_to_string(&enable_path).unwrap(), "0");
519 }
520}