1 #include "config.h"
2
3 #include "selutility.hpp"
4
5 #include <ipmid/api.hpp>
6 #include <ipmid/types.hpp>
7 #include <ipmid/utils.hpp>
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <xyz/openbmc_project/Common/error.hpp>
10
11 #include <charconv>
12 #include <chrono>
13 #include <filesystem>
14 #include <vector>
15
16 extern const ipmi::sensor::InvObjectIDMap invSensors;
17 using namespace phosphor::logging;
18 using InternalFailure =
19 sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
20
21 namespace
22 {
23
24 constexpr auto systemEventRecord = 0x02;
25 constexpr auto generatorID = 0x2000;
26 constexpr auto eventMsgRevision = 0x04;
27 constexpr auto assertEvent = 0x00;
28 constexpr auto deassertEvent = 0x80;
29 constexpr auto selDataSize = 3;
30 constexpr auto oemCDDataSize = 9;
31 constexpr auto oemEFDataSize = 13;
32
33 constexpr auto propAdditionalData = "AdditionalData";
34 constexpr auto propResolved = "Resolved";
35
36 constexpr auto strEventDir = "EVENT_DIR";
37 constexpr auto strGenerateId = "GENERATOR_ID";
38 constexpr auto strRecordType = "RECORD_TYPE";
39 constexpr auto strSensorData = "SENSOR_DATA";
40 constexpr auto strSensorPath = "SENSOR_PATH";
41
42 } // namespace
43
44 namespace ipmi
45 {
46
47 namespace sel
48 {
49
50 namespace internal
51 {
52
isRecordOEM(uint8_t recordType)53 inline bool isRecordOEM(uint8_t recordType)
54 {
55 return recordType != systemEventRecord;
56 }
57
58 using additionalDataMap = std::map<std::string, std::string>;
59 using entryDataMap = std::map<PropertyName, PropertyType>;
60 /** Parse the entry with format like key=val */
parseEntry(const std::string & entry)61 std::pair<std::string, std::string> parseEntry(const std::string& entry)
62 {
63 constexpr auto equalSign = "=";
64 auto pos = entry.find(equalSign);
65 assert(pos != std::string::npos);
66 auto key = entry.substr(0, pos);
67 auto val = entry.substr(pos + 1);
68 return {key, val};
69 }
70
parseAdditionalData(const AdditionalData & data)71 additionalDataMap parseAdditionalData(const AdditionalData& data)
72 {
73 std::map<std::string, std::string> ret;
74
75 for (const auto& d : data)
76 {
77 ret.insert(parseEntry(d));
78 }
79 return ret;
80 }
81
convert(const std::string_view & str,int base=10)82 int convert(const std::string_view& str, int base = 10)
83 {
84 int ret = 0;
85 std::from_chars(str.data(), str.data() + str.size(), ret, base);
86 return ret;
87 }
88
89 // Convert the string to a vector of uint8_t, where the str is formatted as hex
convertVec(const std::string_view & str)90 std::vector<uint8_t> convertVec(const std::string_view& str)
91 {
92 std::vector<uint8_t> ret;
93 auto len = str.size() / 2;
94 ret.reserve(len);
95 for (size_t i = 0; i < len; ++i)
96 {
97 ret.emplace_back(
98 static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
99 }
100 return ret;
101 }
102
103 /** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
constructOEMSEL(uint8_t recordType,std::chrono::milliseconds timestamp,const additionalDataMap & m,GetSELEntryResponse & record)104 void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
105 const additionalDataMap& m, GetSELEntryResponse& record)
106 {
107 auto dataIter = m.find(strSensorData);
108 assert(dataIter != m.end());
109 auto sensorData = convertVec(dataIter->second);
110 if (recordType >= 0xC0 && recordType < 0xE0)
111 {
112 record.event.oemCD.timeStamp = static_cast<uint32_t>(
113 std::chrono::duration_cast<std::chrono::seconds>(timestamp)
114 .count());
115 record.event.oemCD.recordType = recordType;
116 // The ManufactureID and OEM Defined are packed in the sensor data
117 // Fill the 9 bytes of Manufacture ID and oemDefined
118 memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
119 std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
120 }
121 else if (recordType >= 0xE0)
122 {
123 record.event.oemEF.recordType = recordType;
124 // The remaining 13 bytes are the OEM Defined data
125 memcpy(&record.event.oemEF.oemDefined, sensorData.data(),
126 std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
127 }
128 }
129
constructSEL(uint8_t recordType,std::chrono::milliseconds timestamp,const additionalDataMap & m,const entryDataMap &,GetSELEntryResponse & record)130 void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
131 const additionalDataMap& m, const entryDataMap&,
132 GetSELEntryResponse& record)
133 {
134 if (recordType != systemEventRecord)
135 {
136 log<level::ERR>("Invalid recordType");
137 elog<InternalFailure>();
138 }
139
140 // Default values when there is no matched sensor
141 record.event.eventRecord.sensorType = 0;
142 record.event.eventRecord.sensorNum = 0xFF;
143 record.event.eventRecord.eventType = 0;
144
145 auto iter = m.find(strSensorPath);
146 assert(iter != m.end());
147 const auto& sensorPath = iter->second;
148 auto sensorIter = invSensors.find(sensorPath);
149
150 if (sensorIter != invSensors.end())
151 {
152 // There is a matched sensor
153 record.event.eventRecord.sensorType = sensorIter->second.sensorType;
154 record.event.eventRecord.sensorNum = sensorIter->second.sensorID;
155
156 iter = m.find(strEventDir);
157 assert(iter != m.end());
158 auto eventDir = static_cast<uint8_t>(convert(iter->second));
159 uint8_t assert = eventDir ? assertEvent : deassertEvent;
160 record.event.eventRecord.eventType =
161 assert | sensorIter->second.eventReadingType;
162 }
163 record.event.eventRecord.recordType = recordType;
164 record.event.eventRecord.timeStamp = static_cast<uint32_t>(
165 std::chrono::duration_cast<std::chrono::seconds>(timestamp).count());
166 iter = m.find(strGenerateId);
167 assert(iter != m.end());
168 record.event.eventRecord.generatorID =
169 static_cast<uint16_t>(convert(iter->second));
170 record.event.eventRecord.eventMsgRevision = eventMsgRevision;
171 iter = m.find(strSensorData);
172 assert(iter != m.end());
173 auto sensorData = convertVec(iter->second);
174 // The remaining 3 bytes are the sensor data
175 memcpy(&record.event.eventRecord.eventData1, sensorData.data(),
176 std::min(sensorData.size(), static_cast<size_t>(selDataSize)));
177 }
178
179 GetSELEntryResponse
prepareSELEntry(const std::string & objPath,ipmi::sensor::InvObjectIDMap::const_iterator iter)180 prepareSELEntry(const std::string& objPath,
181 ipmi::sensor::InvObjectIDMap::const_iterator iter)
182 {
183 GetSELEntryResponse record{};
184
185 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
186 auto service = ipmi::getService(bus, logEntryIntf, objPath);
187
188 // Read all the log entry properties.
189 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
190 propIntf, "GetAll");
191 methodCall.append(logEntryIntf);
192
193 entryDataMap entryData;
194 try
195 {
196 auto reply = bus.call(methodCall);
197 reply.read(entryData);
198 }
199 catch (const std::exception& e)
200 {
201 log<level::ERR>("Error in reading logging property entries",
202 entry("ERROR=%s", e.what()));
203 elog<InternalFailure>();
204 }
205
206 // Read Id from the log entry.
207 static constexpr auto propId = "Id";
208 auto iterId = entryData.find(propId);
209 if (iterId == entryData.end())
210 {
211 log<level::ERR>("Error in reading Id of logging entry");
212 elog<InternalFailure>();
213 }
214
215 // Read Timestamp from the log entry.
216 static constexpr auto propTimeStamp = "Timestamp";
217 auto iterTimeStamp = entryData.find(propTimeStamp);
218 if (iterTimeStamp == entryData.end())
219 {
220 log<level::ERR>("Error in reading Timestamp of logging entry");
221 elog<InternalFailure>();
222 }
223 std::chrono::milliseconds chronoTimeStamp(
224 std::get<uint64_t>(iterTimeStamp->second));
225
226 bool isFromSELLogger = false;
227 additionalDataMap m;
228
229 // The recordID are with the same offset between different types,
230 // so we are safe to set the recordID here
231 record.event.eventRecord.recordID =
232 static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
233
234 iterId = entryData.find(propAdditionalData);
235 if (iterId != entryData.end())
236 {
237 // Check if it's a SEL from phosphor-sel-logger which shall contain
238 // the record ID, etc
239 const auto& addData = std::get<AdditionalData>(iterId->second);
240 m = parseAdditionalData(addData);
241 auto recordTypeIter = m.find(strRecordType);
242 if (recordTypeIter != m.end())
243 {
244 // It is a SEL from phosphor-sel-logger
245 isFromSELLogger = true;
246 }
247 else
248 {
249 // Not a SEL from phosphor-sel-logger, it shall have a valid
250 // invSensor
251 if (iter == invSensors.end())
252 {
253 log<level::ERR>("System event sensor not found");
254 elog<InternalFailure>();
255 }
256 }
257 }
258
259 if (isFromSELLogger)
260 {
261 // It is expected to be a custom SEL entry
262 auto recordType = static_cast<uint8_t>(convert(m[strRecordType]));
263 auto isOEM = isRecordOEM(recordType);
264 if (isOEM)
265 {
266 constructOEMSEL(recordType, chronoTimeStamp, m, record);
267 }
268 else
269 {
270 constructSEL(recordType, chronoTimeStamp, m, entryData, record);
271 }
272 }
273 else
274 {
275 record.event.eventRecord.timeStamp = static_cast<uint32_t>(
276 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
277 .count());
278
279 record.event.eventRecord.recordType = systemEventRecord;
280 record.event.eventRecord.generatorID = generatorID;
281 record.event.eventRecord.eventMsgRevision = eventMsgRevision;
282
283 record.event.eventRecord.sensorType = iter->second.sensorType;
284 record.event.eventRecord.sensorNum = iter->second.sensorID;
285 record.event.eventRecord.eventData1 = iter->second.eventOffset;
286
287 // Read Resolved from the log entry.
288 auto iterResolved = entryData.find(propResolved);
289 if (iterResolved == entryData.end())
290 {
291 log<level::ERR>("Error in reading Resolved field of logging entry");
292 elog<InternalFailure>();
293 }
294
295 // Evaluate if the event is assertion or deassertion event
296 if (std::get<bool>(iterResolved->second))
297 {
298 record.event.eventRecord.eventType = deassertEvent |
299 iter->second.eventReadingType;
300 }
301 else
302 {
303 record.event.eventRecord.eventType = iter->second.eventReadingType;
304 }
305 }
306
307 return record;
308 }
309
310 } // namespace internal
311
convertLogEntrytoSEL(const std::string & objPath)312 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
313 {
314 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
315
316 static constexpr auto assocIntf =
317 "xyz.openbmc_project.Association.Definitions";
318 static constexpr auto assocProp = "Associations";
319
320 std::vector<ipmi::Association> assocs;
321 try
322 {
323 auto service = ipmi::getService(bus, assocIntf, objPath);
324 auto propValue = ipmi::getDbusProperty(bus, service, objPath, assocIntf,
325 assocProp);
326 assocs = std::get<std::vector<ipmi::Association>>(propValue);
327 }
328 catch (const std::exception& e)
329 {
330 log<level::ERR>("Error in reading Associations interface",
331 entry("ERROR=%s", e.what()));
332 elog<InternalFailure>();
333 }
334
335 /*
336 * Check if the log entry has any callout associations, if there is a
337 * callout association try to match the inventory path to the corresponding
338 * IPMI sensor.
339 */
340 for (const auto& item : assocs)
341 {
342 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
343 {
344 auto iter = invSensors.find(std::get<2>(item));
345 if (iter == invSensors.end())
346 {
347 iter = invSensors.find(BOARD_SENSOR);
348 if (iter == invSensors.end())
349 {
350 log<level::ERR>("Motherboard sensor not found");
351 elog<InternalFailure>();
352 }
353 }
354
355 return internal::prepareSELEntry(objPath, iter);
356 }
357 }
358
359 // If there are no callout associations link the log entry to system event
360 // sensor
361 auto iter = invSensors.find(SYSTEM_SENSOR);
362 return internal::prepareSELEntry(objPath, iter);
363 }
364
getEntryTimeStamp(const std::string & objPath)365 std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
366 {
367 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
368
369 static constexpr auto propTimeStamp = "Timestamp";
370
371 uint64_t timeStamp;
372 try
373 {
374 auto service = ipmi::getService(bus, logEntryIntf, objPath);
375 auto propValue = ipmi::getDbusProperty(bus, service, objPath,
376 logEntryIntf, propTimeStamp);
377 timeStamp = std::get<uint64_t>(propValue);
378 }
379 catch (const std::exception& e)
380 {
381 log<level::ERR>("Error in reading Timestamp from Entry interface",
382 entry("ERROR=%s", e.what()));
383 elog<InternalFailure>();
384 }
385
386 std::chrono::milliseconds chronoTimeStamp(timeStamp);
387
388 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
389 }
390
readLoggingObjectPaths(ObjectPaths & paths)391 void readLoggingObjectPaths(ObjectPaths& paths)
392 {
393 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
394 auto depth = 0;
395 paths.clear();
396
397 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
398 mapperIntf, "GetSubTreePaths");
399 mapperCall.append(logBasePath);
400 mapperCall.append(depth);
401 mapperCall.append(ObjectPaths({logEntryIntf}));
402
403 try
404 {
405 auto reply = bus.call(mapperCall);
406 reply.read(paths);
407 }
408 catch (const sdbusplus::exception_t& e)
409 {
410 if (strcmp(e.name(),
411 "xyz.openbmc_project.Common.Error.ResourceNotFound"))
412 {
413 throw;
414 }
415 }
416
417 std::sort(paths.begin(), paths.end(),
418 [](const std::string& a, const std::string& b) {
419 namespace fs = std::filesystem;
420 fs::path pathA(a);
421 fs::path pathB(b);
422 auto idA = std::stoul(pathA.filename().string());
423 auto idB = std::stoul(pathB.filename().string());
424
425 return idA < idB;
426 });
427 }
428
429 } // namespace sel
430
431 } // namespace ipmi
432