1#[cfg(feature = "collateral_manager")]
7use crate::collateral::{CollateralManager, CollateralTree, ItemPath, PVSS};
8use crate::errata::{Errata, SERVER_LEGACY_PRODUCT_IDS};
9use crate::error::Error;
10use crate::node::Node;
11#[cfg(not(feature = "std"))]
12use alloc::{
13 fmt, format,
14 string::{String, ToString},
15 vec,
16 vec::Vec,
17};
18#[cfg(feature = "std")]
19use std::fmt;
20
21pub mod record_types {
25 pub const PMC: u8 = 0x1;
26 pub const PMC_FW_TRACE: u8 = 0x2;
27 pub const PUNIT: u8 = 0x3;
28 pub const PCORE: u8 = 0x4;
29 pub const ECORE: u8 = 0x6;
30 pub const UNCORE: u8 = 0x8;
31 pub const PMC_TRACE: u8 = 0x11;
32 pub const TCSS: u8 = 0x16;
33 pub const PMC_RST: u8 = 0x17;
34 pub const PCODE: u8 = 0x19;
35 pub const CRASHLOG_AGENT: u8 = 0x1C;
36 pub const BOX: u8 = 0x3D;
37 pub const MCA: u8 = 0x3E;
38}
39
40#[derive(Debug, Default, Clone)]
41pub enum HeaderType {
42 Type0,
43 #[default]
44 Type1,
45 Type2 {
46 timestamp: u64,
47 agent_version: u32,
48 reason: u32,
49 },
50 Type3 {
51 timestamp: u64,
52 agent_version: u32,
53 reason: u32,
54 completion_status: u32,
55 collection_complete: bool,
56 },
57 Type4 {
58 timestamp: u64,
59 agent_version: u32,
60 reason: u32,
61 whoami: u32,
62 misc: u32,
63 },
64 Type5 {
65 timestamp: u64,
66 agent_version: u32,
67 reason: u32,
68 completion_status: u32,
69 collection_complete: bool,
70 error_status: u32,
71 },
72 Type6 {
73 timestamp: u64,
74 agent_version: u32,
75 reason: u32,
76 die_id: u8,
77 socket_id: u8,
78 completion_status_size: u16,
79 completion_status: Vec<u32>,
80 collection_complete: bool,
81 },
82
83 Type0LegacyServer {
84 timestamp: u64,
85 agent_version: u32,
86 reason: u32,
87 die_id: u8,
88 socket_id: u8,
89 completion_status: u32,
90 collection_complete: bool,
91 },
92}
93
94impl HeaderType {
95 fn type2_from_slice(slice: &[u8]) -> Option<Self> {
96 let timestamp = u64::from_le_bytes(slice.get(8..16)?.try_into().ok()?);
97 let agent_version = u32::from_le_bytes(slice.get(16..20)?.try_into().ok()?);
98 let reason = u32::from_le_bytes(slice.get(20..24)?.try_into().ok()?);
99
100 Some(HeaderType::Type2 {
101 timestamp,
102 agent_version,
103 reason,
104 })
105 }
106
107 fn type3_from_slice(slice: &[u8]) -> Option<Self> {
108 let timestamp = u64::from_le_bytes(slice.get(8..16)?.try_into().ok()?);
109 let agent_version = u32::from_le_bytes(slice.get(16..20)?.try_into().ok()?);
110 let reason = u32::from_le_bytes(slice.get(20..24)?.try_into().ok()?);
111 let cs_data = u32::from_le_bytes(slice.get(24..28)?.try_into().ok()?);
112 let completion_status = cs_data & 0x7FFFFFFF;
113 let collection_complete = (cs_data >> 31) != 0;
114
115 Some(HeaderType::Type3 {
116 timestamp,
117 agent_version,
118 reason,
119 completion_status,
120 collection_complete,
121 })
122 }
123
124 fn type4_from_slice(slice: &[u8]) -> Option<Self> {
125 let timestamp = u64::from_le_bytes(slice.get(8..16)?.try_into().ok()?);
126 let agent_version = u32::from_le_bytes(slice.get(16..20)?.try_into().ok()?);
127 let reason = u32::from_le_bytes(slice.get(20..24)?.try_into().ok()?);
128 let whoami = u32::from_le_bytes(slice.get(24..28)?.try_into().ok()?);
129 let misc = u32::from_le_bytes(slice.get(28..32)?.try_into().ok()?);
130
131 Some(HeaderType::Type4 {
132 timestamp,
133 agent_version,
134 reason,
135 whoami,
136 misc,
137 })
138 }
139
140 fn type5_from_slice(slice: &[u8]) -> Option<Self> {
141 let timestamp = u64::from_le_bytes(slice.get(8..16)?.try_into().ok()?);
142 let agent_version = u32::from_le_bytes(slice.get(16..20)?.try_into().ok()?);
143 let reason = u32::from_le_bytes(slice.get(20..24)?.try_into().ok()?);
144 let cs_data = u32::from_le_bytes(slice.get(24..28)?.try_into().ok()?);
145 let completion_status = cs_data & 0x7FFFFFFF;
146 let collection_complete = (cs_data >> 31) != 0;
147 let error_status = u32::from_le_bytes(slice.get(28..32)?.try_into().ok()?);
148
149 Some(HeaderType::Type5 {
150 timestamp,
151 agent_version,
152 reason,
153 completion_status,
154 collection_complete,
155 error_status,
156 })
157 }
158
159 fn type6_from_slice(slice: &[u8]) -> Option<Self> {
160 let timestamp = u64::from_le_bytes(slice.get(8..16)?.try_into().ok()?);
161 let agent_version = u32::from_le_bytes(slice.get(16..20)?.try_into().ok()?);
162 let reason = u32::from_le_bytes(slice.get(20..24)?.try_into().ok()?);
163 let die_skt_info = slice.get(24..28)?;
164
165 let die_id = die_skt_info[0];
166 let socket_id = die_skt_info[1];
167 let completion_status_size = u16::from_le_bytes(die_skt_info[2..4].try_into().ok()?) & 0x7F;
168 let collection_complete = (die_skt_info[3] & 0x80) != 0;
169
170 let completion_status = (0..completion_status_size)
171 .map(|dword| {
172 let index = (28 + dword * 4) as usize;
173 Some(u32::from_le_bytes(
174 slice.get(index..index + 4)?.try_into().ok()?,
175 ))
176 })
177 .collect::<Option<Vec<u32>>>()?;
178
179 Some(HeaderType::Type6 {
180 timestamp,
181 agent_version,
182 reason,
183 die_id,
184 socket_id,
185 completion_status_size,
186 completion_status,
187 collection_complete,
188 })
189 }
190
191 fn type0_legacy_server_from_slice(slice: &[u8]) -> Option<Self> {
192 let reason = u32::from_le_bytes(slice.get(4..8)?.try_into().ok()?);
193 let timestamp = u64::from_le_bytes(slice.get(8..16)?.try_into().ok()?);
194 let agent_version = u32::from_le_bytes(slice.get(20..24)?.try_into().ok()?);
195 let socket_id = *slice.get(24)?;
196 let cs_data = u32::from_le_bytes(slice.get(28..32)?.try_into().ok()?);
197 let completion_status = cs_data & 0x7FFFFFFF;
198 let collection_complete = (cs_data >> 31) != 0;
199
200 let revision = slice.first()?;
202 let die_idx = revision & 0x3;
203
204 let die_id = if ((revision >> 7) & 1) == 1 {
205 die_idx + 9
206 } else {
207 die_idx << 2
208 };
209
210 Some(HeaderType::Type0LegacyServer {
211 timestamp,
212 agent_version,
213 reason,
214 die_id,
215 socket_id,
216 completion_status,
217 collection_complete,
218 })
219 }
220
221 pub fn from_slice(header_type_value: u16, slice: &[u8]) -> Result<Self, Error> {
222 match header_type_value {
223 0 => Ok(HeaderType::Type0),
224 1 => Ok(HeaderType::Type1),
225 2 => Self::type2_from_slice(slice).ok_or(Error::InvalidHeader),
226 3 => Self::type3_from_slice(slice).ok_or(Error::InvalidHeader),
227 4 => Self::type4_from_slice(slice).ok_or(Error::InvalidHeader),
228 5 => Self::type5_from_slice(slice).ok_or(Error::InvalidHeader),
229 6 => Self::type6_from_slice(slice).ok_or(Error::InvalidHeader),
230 type_value => Err(Error::InvalidHeaderType(type_value)),
231 }
232 }
233
234 pub fn from_slice_type0_legacy_server(slice: &[u8]) -> Result<Self, Error> {
235 Self::type0_legacy_server_from_slice(slice).ok_or(Error::InvalidHeader)
236 }
237}
238
239#[derive(Debug, Default, Clone)]
241pub struct Header {
242 pub version: Version,
244 pub size: RecordSize,
246 pub header_type: HeaderType,
248}
249
250impl Header {
251 pub fn from_slice(slice: &[u8]) -> Result<Option<Self>, Error> {
253 let Some(version) = Version::from_slice(slice) else {
254 return Ok(None);
256 };
257 let errata = Errata::from_version(&version);
258
259 let size = if errata.type0_legacy_server {
260 RecordSize::from_slice_type0_legacy_server(slice).ok_or(Error::InvalidHeader)?
261 } else {
262 RecordSize::from_slice(slice).ok_or(Error::InvalidHeader)?
263 };
264
265 let header_type = if errata.type0_legacy_server {
266 HeaderType::from_slice_type0_legacy_server(slice)?
267 } else {
268 HeaderType::from_slice(version.header_type, slice)?
269 };
270
271 Ok(Some(Header {
272 version,
273 size,
274 header_type,
275 }))
276 }
277
278 #[inline]
280 fn record_size_granularity(&self) -> usize {
281 if self.version.into_errata().core_record_size_bytes {
282 return 1;
283 }
284 4
285 }
286
287 #[inline]
289 pub fn record_size(&self) -> usize {
290 (self.size.record_size as usize + self.size.extended_record_size as usize)
291 * self.record_size_granularity()
292 }
293
294 #[inline]
296 pub fn extended_record_offset(&self) -> Option<usize> {
297 if self.size.extended_record_size > 0 {
298 Some(self.size.record_size as usize * self.record_size_granularity())
299 } else {
300 None
301 }
302 }
303
304 #[inline]
306 pub fn revision(&self) -> u32 {
307 self.version.revision
308 }
309
310 #[inline]
312 pub fn product_id(&self) -> u32 {
313 self.version.product_id
314 }
315
316 #[cfg(feature = "collateral_manager")]
318 pub fn product<'a, T: CollateralTree>(
319 &self,
320 cm: &'a CollateralManager<T>,
321 ) -> Result<&'a str, Error> {
322 cm.target_info
323 .get(&self.product_id())
324 .map(|target_info| target_info.product.as_str())
325 .ok_or_else(|| Error::InvalidProductID(self.product_id()))
326 }
327
328 #[cfg(feature = "collateral_manager")]
330 pub fn variant<'a, T: CollateralTree>(&self, cm: &'a CollateralManager<T>) -> Option<&'a str> {
331 cm.target_info
332 .get(&self.product_id())
333 .map(|target_info| target_info.variant.as_str())
334 }
335
336 pub fn socket_id(&self) -> u8 {
338 match self.header_type {
339 HeaderType::Type6 { socket_id, .. } => socket_id,
340 HeaderType::Type0LegacyServer { socket_id, .. } => socket_id,
341 _ => 0,
342 }
343 }
344
345 pub fn die_id(&self) -> Option<u8> {
347 match self.header_type {
348 HeaderType::Type6 { die_id, .. } => Some(die_id),
349 HeaderType::Type0LegacyServer { die_id, .. } => Some(die_id),
350 _ => None,
351 }
352 }
353
354 #[cfg(feature = "collateral_manager")]
358 pub fn die<'a, T: CollateralTree>(&self, cm: &'a CollateralManager<T>) -> Option<&'a str> {
359 self.get_die_name(&self.die_id()?, cm)
360 }
361
362 #[cfg(feature = "collateral_manager")]
363 pub(crate) fn get_die_name<'a, T: CollateralTree>(
364 &self,
365 die_id: &u8,
366 cm: &'a CollateralManager<T>,
367 ) -> Option<&'a str> {
368 let target_info = cm.target_info.get(&self.product_id())?;
369 let die_name = target_info.die_id.get(die_id)?;
370 Some(die_name)
371 }
372
373 pub fn record_type(&self) -> Result<&'static str, Error> {
375 self.version.record_type_as_str()
376 }
377
378 #[cfg(feature = "collateral_manager")]
379 pub(super) fn decode_definitions_paths<T: CollateralTree>(
380 &self,
381 cm: &CollateralManager<T>,
382 ) -> Result<Vec<ItemPath>, Error> {
383 let record_type = self.record_type()?;
384 let revision = self.revision().to_string();
385
386 Ok(if let Some(die) = self.die(cm) {
387 let die_id = die.trim_end_matches(char::is_numeric);
388 vec![ItemPath::new([
389 "decode-defs",
390 record_type,
391 die_id,
392 &revision,
393 ])]
394 } else {
395 vec![
396 ItemPath::new(["decode-defs", record_type, &revision]),
397 ItemPath::new(["decode-defs", record_type, "all"]),
398 ]
399 })
400 }
401
402 #[cfg(feature = "collateral_manager")]
403 pub fn pvss<T: CollateralTree>(&self, cm: &CollateralManager<T>) -> Result<PVSS, Error> {
405 let product = match self.product(cm) {
406 Err(Error::InvalidProductID(0)) => "all",
407 res => res?,
408 };
409 let variant = self.variant(cm).unwrap_or("all");
410
411 Ok(PVSS {
412 product: product.into(),
413 variant: variant.into(),
414 ..PVSS::default()
415 })
416 }
417
418 pub fn header_size(&self) -> usize {
420 match self.header_type {
421 HeaderType::Type0 | HeaderType::Type1 => 8,
422 HeaderType::Type2 { .. } => 24,
423 HeaderType::Type3 { .. } => 28,
424 HeaderType::Type4 { .. } => 32,
425 HeaderType::Type5 { .. } => 32,
426 HeaderType::Type6 {
427 completion_status_size,
428 ..
429 } => 28 + completion_status_size as usize * 4,
430 HeaderType::Type0LegacyServer { .. } => 32,
431 }
432 }
433
434 #[cfg(feature = "collateral_manager")]
435 pub(super) fn get_root_path_using_cm<T: CollateralTree>(
436 &self,
437 cm: &CollateralManager<T>,
438 ) -> Option<String> {
439 if let HeaderType::Type6 { socket_id, .. } | HeaderType::Type0LegacyServer { socket_id, .. } =
440 self.header_type
441 && let Some(die) = self.die(cm)
442 {
443 return Some(format!("processors.cpu{socket_id}.{die}"));
444 }
445
446 None
447 }
448
449 pub(super) fn get_root_path(&self) -> Option<String> {
450 if let HeaderType::Type6 {
451 socket_id, die_id, ..
452 }
453 | HeaderType::Type0LegacyServer {
454 socket_id, die_id, ..
455 } = self.header_type
456 {
457 return Some(format!("processors.cpu{socket_id}.die{die_id}"));
458 }
459
460 None
461 }
462}
463
464impl fmt::Display for Header {
465 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466 let record_type = self.record_type().unwrap_or("RECORD");
467 let version = format!(
468 "product_id=0x{0:x}, record_type=0x{1:x}, revision=0x{2:x}",
469 self.version.product_id, self.version.record_type, self.version.revision
470 );
471 let header_type = match self.header_type {
472 HeaderType::Type6 {
473 socket_id, die_id, ..
474 }
475 | HeaderType::Type0LegacyServer {
476 socket_id, die_id, ..
477 } => {
478 format!("die_id={die_id}, socket_id={socket_id}")
479 }
480 _ => "..".to_string(),
481 };
482
483 write!(f, "{record_type} - ({version}, {header_type})")
484 }
485}
486
487#[derive(Clone, Debug, Default)]
489pub struct Version {
490 pub revision: u32,
492 pub header_type: u16,
494 pub product_id: u32,
496 pub record_type: u8,
498 pub consumed: bool,
500 pub cldic: bool,
502}
503
504impl Version {
505 pub fn from_slice(slice: &[u8]) -> Option<Self> {
507 let version = u32::from_le_bytes(slice.get(0..4)?.try_into().ok()?);
508 log::trace!("Decoding record version: {:#x}", version);
509
510 if version == 0 || version == 0xdeadbeef {
511 return None;
513 }
514
515 Some(Version {
516 cldic: (version >> 30) & 1 == 1,
517 consumed: (version >> 31) & 1 == 1,
518 revision: version & 0xFF,
519 header_type: ((version >> 8) & 0xF) as u16,
520 product_id: (version >> 12) & 0xFFF,
521 record_type: ((version >> 24) & 0x3F) as u8,
522 })
523 }
524
525 pub fn as_u32(&self) -> u32 {
526 ((self.consumed as u32) << 31)
527 | ((self.cldic as u32) << 30)
528 | ((self.record_type as u32) << 24)
529 | (self.product_id << 12)
530 | ((self.header_type as u32) << 8)
531 | self.revision
532 }
533
534 fn record_type_as_str(&self) -> Result<&'static str, Error> {
535 Ok(match self.record_type {
536 record_types::PMC => "PMC",
537 record_types::PMC_FW_TRACE => "PMC_FW_Trace",
538 record_types::PUNIT => "Punit",
539 record_types::PCORE => "PCORE",
540 record_types::ECORE => "ECORE",
541 record_types::UNCORE => "UNCORE",
542 record_types::PMC_TRACE => "PMC_TRACE",
543 record_types::TCSS => "TCSS",
544 record_types::PMC_RST => "PMC_RST",
545 record_types::PCODE => "PCODE",
546 record_types::CRASHLOG_AGENT => "CRASHLOG_AGENT",
547 record_types::BOX => "BOX",
548 record_types::MCA => "MCA",
549 rt => return Err(Error::InvalidRecordType(rt)),
550 })
551 }
552
553 pub fn into_errata(&self) -> Errata {
554 let type0_legacy_server =
555 self.header_type == 0 && SERVER_LEGACY_PRODUCT_IDS.contains(&self.product_id);
556 let type0_legacy_server_box = type0_legacy_server && self.record_type == 0x4;
557 let core_record_size_bytes = !type0_legacy_server
558 && ((self.record_type == record_types::ECORE && self.product_id < 0x96)
559 || (self.record_type == record_types::PCORE && self.product_id < 0x71));
560
561 Errata {
562 type0_legacy_server,
563 type0_legacy_server_box,
564 core_record_size_bytes,
565 }
566 }
567}
568
569impl fmt::Display for Version {
570 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
571 let record_type = self.record_type_as_str().unwrap_or("UNKNOWN");
572 write!(f, "{} revision {}", record_type, self.revision)
573 }
574}
575
576#[derive(Debug, Default, Clone)]
578pub struct RecordSize {
579 pub record_size: u16,
581 pub extended_record_size: u16,
583}
584
585impl RecordSize {
586 pub fn from_slice(slice: &[u8]) -> Option<Self> {
588 Some(RecordSize {
589 record_size: u16::from_le_bytes(slice.get(4..6)?.try_into().ok()?),
590 extended_record_size: u16::from_le_bytes(slice.get(6..8)?.try_into().ok()?),
591 })
592 }
593
594 pub fn from_slice_type0_legacy_server(slice: &[u8]) -> Option<Self> {
596 Some(RecordSize {
597 record_size: u16::from_le_bytes(slice.get(16..18)?.try_into().ok()?),
598 extended_record_size: 0,
599 })
600 }
601}
602
603impl From<&RecordSize> for Node {
604 fn from(size: &RecordSize) -> Self {
605 let mut node = Node::section("record_size");
606 node.add(Node::field("record_size", size.record_size as u64));
607 node.add(Node::field(
608 "extended_record_size",
609 size.extended_record_size as u64,
610 ));
611 node
612 }
613}
614
615impl From<&Version> for Node {
616 fn from(version: &Version) -> Self {
617 let mut node = Node::field("version", version.as_u32() as u64);
618 node.add(Node::field("revision", version.revision as u64));
619 node.add(Node::field("header_type", version.header_type as u64));
620 node.add(Node::field("product_id", version.product_id as u64));
621 node.add(Node::field("record_type", version.record_type as u64));
622 node
623 }
624}
625
626impl From<&Header> for Node {
627 fn from(header: &Header) -> Self {
628 let mut node = Node::section("hdr");
629 node.add(Node::from(&header.version));
630 node.add(Node::from(&header.size));
631
632 match header.header_type {
633 HeaderType::Type2 {
634 timestamp,
635 agent_version,
636 reason,
637 } => {
638 node.add(Node::field("timestamp", timestamp));
639 node.add(Node::field("agent_version", agent_version as u64));
640 node.add(Node::field("reason", reason as u64));
641 }
642 HeaderType::Type3 {
643 timestamp,
644 agent_version,
645 reason,
646 completion_status,
647 collection_complete,
648 } => {
649 node.add(Node::field("timestamp", timestamp));
650 node.add(Node::field("agent_version", agent_version as u64));
651 node.add(Node::field("reason", reason as u64));
652
653 let mut completion_status_node = Node::section("completion_status");
654 completion_status_node
655 .add(Node::field("completion_status", completion_status as u64));
656 completion_status_node.add(Node::field(
657 "record_collection_completed",
658 collection_complete as u64,
659 ));
660 node.add(completion_status_node);
661 }
662 HeaderType::Type4 {
663 timestamp,
664 agent_version,
665 reason,
666 whoami,
667 misc,
668 } => {
669 node.add(Node::field("timestamp", timestamp));
670 node.add(Node::field("agent_version", agent_version as u64));
671 node.add(Node::field("reason", reason as u64));
672 node.add(Node::field("whoami", whoami as u64));
673 node.add(Node::field("misc", misc as u64));
674 }
675 HeaderType::Type5 {
676 timestamp,
677 agent_version,
678 reason,
679 completion_status,
680 collection_complete,
681 error_status,
682 } => {
683 node.add(Node::field("timestamp", timestamp));
684 node.add(Node::field("agent_version", agent_version as u64));
685 node.add(Node::field("reason", reason as u64));
686 node.add(Node::field("error_status", error_status as u64));
687
688 let mut completion_status_node = Node::section("completion_status");
689 completion_status_node
690 .add(Node::field("completion_status", completion_status as u64));
691 completion_status_node.add(Node::field(
692 "record_collection_completed",
693 collection_complete as u64,
694 ));
695 node.add(completion_status_node);
696 }
697 HeaderType::Type6 {
698 timestamp,
699 agent_version,
700 reason,
701 die_id,
702 socket_id,
703 ref completion_status,
704 completion_status_size,
705 collection_complete,
706 } => {
707 node.add(Node::field("timestamp", timestamp));
708 node.add(Node::field("agent_version", agent_version as u64));
709 node.add(Node::field("reason", reason as u64));
710 let mut die_skt_info = Node::section("die_skt_info");
711 die_skt_info.add(Node::field("die_id", die_id as u64));
712 die_skt_info.add(Node::field("socket_id", socket_id as u64));
713 die_skt_info.add(Node::field(
714 "completion_status_size",
715 completion_status_size as u64,
716 ));
717 die_skt_info.add(Node::field(
718 "record_collection_completed",
719 collection_complete as u64,
720 ));
721 node.add(die_skt_info);
722
723 for (i, completion_status) in completion_status.iter().enumerate() {
724 node.add(Node::field(
725 &format!("completion_status{i}"),
726 *completion_status as u64,
727 ));
728 }
729 }
730 HeaderType::Type0LegacyServer {
731 timestamp,
732 agent_version,
733 reason,
734 die_id,
735 socket_id,
736 completion_status,
737 collection_complete,
738 } => {
739 node.add(Node::field("timestamp", timestamp));
740 node.add(Node::field("agent_version", agent_version as u64));
741 node.add(Node::field("reason", reason as u64));
742 node.add(Node::field("die_id", die_id as u64));
743 node.add(Node::field("socket_id", socket_id as u64));
744 node.add(Node::field("completion_status", completion_status as u64));
745 node.add(Node::field(
746 "record_collection_completed",
747 collection_complete as u64,
748 ));
749 }
750 _ => (),
751 }
752
753 node
754 }
755}