ipmi_rs/storage/sel/
mod.rs

1use crate::{
2    connection::{Channel, LogicalUnit},
3    fmt::LogItem,
4    log_vec, Loggable,
5};
6
7use super::Timestamp;
8
9mod get_alloc_info;
10pub use get_alloc_info::{AllocInfo as SelAllocInfo, GetAllocInfo as GetSelAllocInfo};
11
12mod get_entry;
13pub use get_entry::{EntryInfo as SelEntryInfo, GetEntry as GetSelEntry};
14
15mod get_info;
16pub use get_info::{Command as SelCommand, GetInfo as GetSelInfo, Info as SelInfo};
17
18#[derive(Debug, Clone, Copy, PartialEq)]
19pub struct RecordId(u16);
20
21impl RecordId {
22    pub const FIRST: Self = Self(0x0000);
23    pub const LAST: Self = Self(0xFFFF);
24
25    pub fn new(id: u16) -> Option<Self> {
26        if RecordId(id) == Self::FIRST || RecordId(id) == Self::LAST {
27            None
28        } else {
29            Some(Self(id))
30        }
31    }
32
33    pub(crate) fn new_raw(id: u16) -> Self {
34        RecordId(id)
35    }
36
37    pub fn value(&self) -> u16 {
38        self.0
39    }
40
41    pub fn is_first(&self) -> bool {
42        self == &Self::FIRST
43    }
44
45    pub fn is_last(&self) -> bool {
46        self == &Self::LAST
47    }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
51enum SelRecordType {
52    System,
53    TimestampedOem(u8),
54    NonTimestampedOem(u8),
55    Unknown(u8),
56}
57
58impl From<u8> for SelRecordType {
59    fn from(value: u8) -> Self {
60        match value {
61            0x02 => Self::System,
62            0xC0..=0xDF => Self::TimestampedOem(value),
63            0xE0..=0xFF => Self::NonTimestampedOem(value),
64            v => Self::Unknown(v),
65        }
66    }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq)]
70pub enum EventGenerator {
71    RqSAAndLun {
72        i2c_addr: u8,
73        channel_number: Channel,
74        lun: LogicalUnit,
75    },
76    SoftwareId {
77        software_id: u8,
78        channel_number: Channel,
79    },
80}
81
82impl From<(u8, u8)> for EventGenerator {
83    fn from(value: (u8, u8)) -> Self {
84        let is_software_id = (value.0 & 0x1) == 0x1;
85        let i2c_or_sid = (value.0 >> 1) & 0x7F;
86
87        // NOTE(unwrap): value is in valid range due to mask.
88        let channel_number = Channel::new((value.1 >> 4) & 0xF).unwrap();
89
90        if is_software_id {
91            Self::SoftwareId {
92                software_id: i2c_or_sid,
93                channel_number,
94            }
95        } else {
96            let lun = LogicalUnit::from_low_bits(value.1);
97
98            Self::RqSAAndLun {
99                i2c_addr: i2c_or_sid,
100                channel_number,
101                lun,
102            }
103        }
104    }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq)]
108pub enum EventMessageRevision {
109    V2_0,
110    V1_0,
111    Unknown(u8),
112}
113
114impl From<u8> for EventMessageRevision {
115    fn from(value: u8) -> Self {
116        match value {
117            0x04 => Self::V2_0,
118            0x03 => Self::V1_0,
119            v => Self::Unknown(v),
120        }
121    }
122}
123
124#[derive(Debug, Clone, Copy, PartialEq)]
125pub enum EventDirection {
126    Assert,
127    Deassert,
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub enum Entry {
132    System {
133        record_id: RecordId,
134        timestamp: Timestamp,
135        generator_id: EventGenerator,
136        event_message_format: EventMessageRevision,
137        sensor_type: u8,
138        sensor_number: u8,
139        event_direction: EventDirection,
140        event_type: u8,
141        event_data: [u8; 3],
142    },
143    OemTimestamped {
144        record_id: RecordId,
145        ty: u8,
146        timestamp: Timestamp,
147        manufacturer_id: u32,
148        data: [u8; 6],
149    },
150    OemNotTimestamped {
151        record_id: RecordId,
152        ty: u8,
153        data: [u8; 13],
154    },
155}
156
157#[derive(Debug, Clone, Copy, PartialEq)]
158pub enum ParseEntryError {
159    NotEnoughData,
160    UnknownRecordType(u8),
161}
162
163impl Entry {
164    pub fn parse(data: &[u8]) -> Result<Self, ParseEntryError> {
165        if data.len() < 15 {
166            return Err(ParseEntryError::NotEnoughData);
167        }
168
169        let record_id = RecordId(u16::from_le_bytes([data[0], data[1]]));
170        let record_type = SelRecordType::from(data[2]);
171        let timestamp = u32::from_le_bytes([data[3], data[4], data[5], data[6]]);
172
173        match record_type {
174            SelRecordType::System => {
175                let generator_id = EventGenerator::from((data[7], data[8]));
176                let event_message_format = EventMessageRevision::from(data[9]);
177                let sensor_type = data[10];
178                let sensor_number = data[11];
179                let event_direction = if (data[12] & 0x80) == 0x80 {
180                    EventDirection::Assert
181                } else {
182                    EventDirection::Deassert
183                };
184                let event_type = data[12] & 0x7F;
185                let event_data = [data[13], data[14], data[15]];
186                Ok(Self::System {
187                    record_id,
188                    timestamp: Timestamp::from(timestamp),
189                    generator_id,
190                    event_message_format,
191                    sensor_type,
192                    sensor_number,
193                    event_direction,
194                    event_type,
195                    event_data,
196                })
197            }
198            SelRecordType::TimestampedOem(v) => Ok(Self::OemTimestamped {
199                record_id,
200                ty: v,
201                timestamp: Timestamp::from(timestamp),
202                manufacturer_id: u32::from_le_bytes([data[7], data[8], data[9], 0]),
203                data: [data[10], data[11], data[12], data[13], data[14], data[15]],
204            }),
205            SelRecordType::NonTimestampedOem(v) => Ok(Self::OemNotTimestamped {
206                record_id,
207                ty: v,
208                data: [
209                    data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10],
210                    data[11], data[12], data[13], data[14], data[15],
211                ],
212            }),
213            SelRecordType::Unknown(v) => Err(ParseEntryError::UnknownRecordType(v)),
214        }
215    }
216}
217
218impl Loggable for Entry {
219    fn as_log(&self) -> Vec<LogItem> {
220        match self {
221            Entry::System {
222                record_id,
223                timestamp,
224                generator_id,
225                event_message_format,
226                sensor_type,
227                sensor_number,
228                event_direction,
229                event_type,
230                event_data,
231            } => {
232                let format = match event_message_format {
233                    EventMessageRevision::V2_0 => "2.0".into(),
234                    EventMessageRevision::V1_0 => "1.0".into(),
235                    EventMessageRevision::Unknown(v) => format!("Unknown (0x{:02X})", v),
236                };
237
238                let event_dir = match event_direction {
239                    EventDirection::Assert => "Asserted",
240                    EventDirection::Deassert => "Deasserted",
241                };
242
243                log_vec![
244                    (0, "SEL entry"),
245                    (1, "Record type", "System (0x02)"),
246                    (1, "Record ID", format!("0x{:04X}", record_id.value())),
247                    (1, "Time", timestamp),
248                    (1, "Generator", format!("{:?}", generator_id)),
249                    (1, "Format revision", format),
250                    (1, "Sensor type", format!("0x{sensor_type:02X}")),
251                    (1, "Sensor number", format!("0x{sensor_number:02X}")),
252                    (1, "Assertion state", event_dir),
253                    (1, "Event type", format!("0x{event_type:02X}")),
254                    (1, "Data", format!("{event_data:02X?}")),
255                ]
256            }
257            Entry::OemTimestamped {
258                record_id,
259                ty,
260                timestamp,
261                manufacturer_id,
262                data,
263            } => {
264                log_vec![
265                    (0, "SEL entry"),
266                    (1, "Record type", format!("Timestamped OEM (0x{ty:08X})")),
267                    (1, "Record ID", format!("0x{:04X}", record_id.value())),
268                    (1, "Type", format!("{ty:02X}")),
269                    (1, "Timestamp", timestamp),
270                    (1, "Manufacturer ID", format!("{manufacturer_id:02X?}")),
271                    (1, "Data", format!("{data:02X?}")),
272                ]
273            }
274            Entry::OemNotTimestamped {
275                record_id,
276                ty,
277                data,
278            } => {
279                log_vec![
280                    (0, "SEL entry"),
281                    (1, "Record type", format!("Not timestamp OEM (0x{ty:08X}")),
282                    (1, "Record ID", format!("0x{:04X}", record_id.value())),
283                    (1, "Type", format!("0x{ty:02X}")),
284                    (1, "Data", format!("{data:02X?}"))
285                ]
286            }
287        }
288    }
289}