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