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