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