blob: 344201e88e435c8a6046f4c000f795b14f16f01e [file] [log] [blame]
Patrick Venture46470a32018-09-07 19:26:25 -07001#include "config.h"
2
3#include "selutility.hpp"
4
Lei YU5015e952020-12-03 14:01:31 +08005#include <charconv>
Tom Joseph6b7a1432017-05-19 10:43:36 +05306#include <chrono>
Vernon Mauerybdda8002019-02-26 10:18:51 -08007#include <filesystem>
Vernon Mauerye08fbff2019-04-03 09:19:34 -07008#include <ipmid/api.hpp>
Vernon Mauery33250242019-03-12 16:49:26 -07009#include <ipmid/types.hpp>
Vernon Mauery6a98fe72019-03-11 15:57:48 -070010#include <ipmid/utils.hpp>
Patrick Venture3a5071a2018-09-12 13:27:42 -070011#include <phosphor-logging/elog-errors.hpp>
Tom Joseph6b7a1432017-05-19 10:43:36 +053012#include <vector>
Patrick Venture3a5071a2018-09-12 13:27:42 -070013#include <xyz/openbmc_project/Common/error.hpp>
Patrick Venture46470a32018-09-07 19:26:25 -070014
Tom Joseph6b7a1432017-05-19 10:43:36 +053015extern const ipmi::sensor::InvObjectIDMap invSensors;
16using namespace phosphor::logging;
17using InternalFailure =
Patrick Venture0b02be92018-08-31 11:55:55 -070018 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
Tom Joseph6b7a1432017-05-19 10:43:36 +053019
Lei YU5015e952020-12-03 14:01:31 +080020namespace
21{
22
23constexpr auto systemEventRecord = 0x02;
Lei YU07c15b72020-12-06 15:08:06 +080024constexpr auto generatorID = 0x2000;
25constexpr auto eventMsgRevision = 0x04;
26constexpr auto assertEvent = 0x00;
27constexpr auto deassertEvent = 0x80;
28constexpr auto selDataSize = 3;
29constexpr auto oemCDDataSize = 9;
30constexpr auto oemEFDataSize = 13;
Lei YU5015e952020-12-03 14:01:31 +080031
Lei YU719a41c2020-12-03 17:50:44 +080032constexpr auto propAdditionalData = "AdditionalData";
Lei YU07c15b72020-12-06 15:08:06 +080033constexpr auto propResolved = "Resolved";
34
Lei YU719a41c2020-12-03 17:50:44 +080035constexpr auto strEventDir = "EVENT_DIR";
36constexpr auto strGenerateId = "GENERATOR_ID";
37constexpr auto strRecordType = "RECORD_TYPE";
38constexpr auto strSensorData = "SENSOR_DATA";
39constexpr auto strSensorPath = "SENSOR_PATH";
40
Lei YU5015e952020-12-03 14:01:31 +080041} // namespace
42
Tom Joseph6b7a1432017-05-19 10:43:36 +053043namespace ipmi
44{
45
46namespace sel
47{
48
49namespace internal
50{
51
Lei YU5015e952020-12-03 14:01:31 +080052inline bool isRecordOEM(uint8_t recordType)
53{
54 return recordType != systemEventRecord;
55}
56
Lei YU719a41c2020-12-03 17:50:44 +080057using additionalDataMap = std::map<std::string, std::string>;
Lei YU07c15b72020-12-06 15:08:06 +080058using entryDataMap = std::map<PropertyName, PropertyType>;
Lei YU5015e952020-12-03 14:01:31 +080059/** Parse the entry with format like key=val */
60std::pair<std::string, std::string> parseEntry(const std::string& entry)
61{
62 constexpr auto equalSign = "=";
63 auto pos = entry.find(equalSign);
64 assert(pos != std::string::npos);
65 auto key = entry.substr(0, pos);
66 auto val = entry.substr(pos + 1);
67 return {key, val};
68}
69
Lei YU719a41c2020-12-03 17:50:44 +080070additionalDataMap parseAdditionalData(const AdditionalData& data)
Lei YU5015e952020-12-03 14:01:31 +080071{
72 std::map<std::string, std::string> ret;
73
74 for (const auto& d : data)
75 {
76 ret.insert(parseEntry(d));
77 }
78 return ret;
79}
80
Lei YU07c15b72020-12-06 15:08:06 +080081int convert(const std::string_view& str, int base = 10)
Lei YU5015e952020-12-03 14:01:31 +080082{
83 int ret;
84 std::from_chars(str.data(), str.data() + str.size(), ret, base);
Lei YU07c15b72020-12-06 15:08:06 +080085 return ret;
Lei YU5015e952020-12-03 14:01:31 +080086}
87
88// Convert the string to a vector of uint8_t, where the str is formatted as hex
89std::vector<uint8_t> convertVec(const std::string_view& str)
90{
91 std::vector<uint8_t> ret;
92 auto len = str.size() / 2;
93 ret.reserve(len);
94 for (size_t i = 0; i < len; ++i)
95 {
Lei YU07c15b72020-12-06 15:08:06 +080096 ret.emplace_back(
97 static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
Lei YU5015e952020-12-03 14:01:31 +080098 }
99 return ret;
100}
101
Lei YU719a41c2020-12-03 17:50:44 +0800102/** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
Lei YU07c15b72020-12-06 15:08:06 +0800103void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
Lei YU719a41c2020-12-03 17:50:44 +0800104 const additionalDataMap& m, GetSELEntryResponse& record)
105{
106 auto dataIter = m.find(strSensorData);
107 assert(dataIter != m.end());
108 auto sensorData = convertVec(dataIter->second);
109 if (recordType >= 0xC0 && recordType < 0xE0)
110 {
111 record.event.oemCD.timeStamp = static_cast<uint32_t>(
112 std::chrono::duration_cast<std::chrono::seconds>(timestamp)
113 .count());
114 record.event.oemCD.recordType = recordType;
115 // The ManufactureID and OEM Defined are packed in the sensor data
116 // Fill the 9 bytes of Manufacture ID and oemDefined
117 memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
Lei YU07c15b72020-12-06 15:08:06 +0800118 std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
Lei YU719a41c2020-12-03 17:50:44 +0800119 }
120 else if (recordType >= 0xE0)
121 {
122 record.event.oemEF.recordType = recordType;
123 // The remaining 13 bytes are the OEM Defined data
124 memcpy(&record.event.oemEF.oemDefined, sensorData.data(),
Lei YU07c15b72020-12-06 15:08:06 +0800125 std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
Lei YU719a41c2020-12-03 17:50:44 +0800126 }
127}
128
Lei YU07c15b72020-12-06 15:08:06 +0800129void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
Willy Tu11d68892022-01-20 10:37:34 -0800130 const additionalDataMap& m, const entryDataMap&,
Lei YU07c15b72020-12-06 15:08:06 +0800131 GetSELEntryResponse& record)
132{
133 if (recordType != systemEventRecord)
134 {
135 log<level::ERR>("Invalid recordType");
136 elog<InternalFailure>();
137 }
138
139 // Default values when there is no matched sensor
140 record.event.eventRecord.sensorType = 0;
141 record.event.eventRecord.sensorNum = 0xFF;
142 record.event.eventRecord.eventType = 0;
143
144 auto iter = m.find(strSensorPath);
145 assert(iter != m.end());
146 const auto& sensorPath = iter->second;
147 auto sensorIter = invSensors.find(sensorPath);
148
149 if (sensorIter != invSensors.end())
150 {
151 // There is a matched sensor
152 record.event.eventRecord.sensorType = sensorIter->second.sensorType;
153 record.event.eventRecord.sensorNum = sensorIter->second.sensorID;
154
155 iter = m.find(strEventDir);
156 assert(iter != m.end());
157 auto eventDir = static_cast<uint8_t>(convert(iter->second));
158 uint8_t assert = eventDir ? assertEvent : deassertEvent;
159 record.event.eventRecord.eventType =
160 assert | sensorIter->second.eventReadingType;
161 }
162 record.event.eventRecord.recordType = recordType;
163 record.event.eventRecord.timeStamp = static_cast<uint32_t>(
164 std::chrono::duration_cast<std::chrono::seconds>(timestamp).count());
165 iter = m.find(strGenerateId);
166 assert(iter != m.end());
167 record.event.eventRecord.generatorID =
168 static_cast<uint16_t>(convert(iter->second));
169 record.event.eventRecord.eventMsgRevision = eventMsgRevision;
170 iter = m.find(strSensorData);
171 assert(iter != m.end());
172 auto sensorData = convertVec(iter->second);
173 // The remaining 3 bytes are the sensor data
174 memcpy(&record.event.eventRecord.eventData1, sensorData.data(),
175 std::min(sensorData.size(), static_cast<size_t>(selDataSize)));
176}
177
Patrick Venture0b02be92018-08-31 11:55:55 -0700178GetSELEntryResponse
179 prepareSELEntry(const std::string& objPath,
180 ipmi::sensor::InvObjectIDMap::const_iterator iter)
Tom Joseph6b7a1432017-05-19 10:43:36 +0530181{
Patrick Venture0b02be92018-08-31 11:55:55 -0700182 GetSELEntryResponse record{};
Tom Joseph6b7a1432017-05-19 10:43:36 +0530183
184 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
185 auto service = ipmi::getService(bus, logEntryIntf, objPath);
186
187 // Read all the log entry properties.
Patrick Venture0b02be92018-08-31 11:55:55 -0700188 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
189 propIntf, "GetAll");
Tom Joseph6b7a1432017-05-19 10:43:36 +0530190 methodCall.append(logEntryIntf);
191
192 auto reply = bus.call(methodCall);
193 if (reply.is_method_error())
194 {
195 log<level::ERR>("Error in reading logging property entries");
196 elog<InternalFailure>();
197 }
198
Lei YU07c15b72020-12-06 15:08:06 +0800199 entryDataMap entryData;
Tom Joseph6b7a1432017-05-19 10:43:36 +0530200 reply.read(entryData);
201
202 // Read Id from the log entry.
203 static constexpr auto propId = "Id";
204 auto iterId = entryData.find(propId);
205 if (iterId == entryData.end())
206 {
207 log<level::ERR>("Error in reading Id of logging entry");
208 elog<InternalFailure>();
209 }
210
Tom Joseph6b7a1432017-05-19 10:43:36 +0530211 // Read Timestamp from the log entry.
212 static constexpr auto propTimeStamp = "Timestamp";
213 auto iterTimeStamp = entryData.find(propTimeStamp);
214 if (iterTimeStamp == entryData.end())
215 {
216 log<level::ERR>("Error in reading Timestamp of logging entry");
217 elog<InternalFailure>();
218 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530219 std::chrono::milliseconds chronoTimeStamp(
Vernon Maueryf442e112019-04-09 11:44:36 -0700220 std::get<uint64_t>(iterTimeStamp->second));
Tom Joseph6b7a1432017-05-19 10:43:36 +0530221
Lei YU690f4d52021-04-28 19:05:24 +0800222 bool isFromSELLogger = false;
223 additionalDataMap m;
224
225 // The recordID are with the same offset between different types,
226 // so we are safe to set the recordID here
227 record.event.eventRecord.recordID =
228 static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
229
230 iterId = entryData.find(propAdditionalData);
231 if (iterId != entryData.end())
232 {
233 // Check if it's a SEL from phosphor-sel-logger which shall contain
234 // the record ID, etc
235 const auto& addData = std::get<AdditionalData>(iterId->second);
236 m = parseAdditionalData(addData);
237 auto recordTypeIter = m.find(strRecordType);
238 if (recordTypeIter != m.end())
239 {
240 // It is a SEL from phosphor-sel-logger
241 isFromSELLogger = true;
242 }
243 else
244 {
245 // Not a SEL from phosphor-sel-logger, it shall have a valid
246 // invSensor
247 if (iter == invSensors.end())
248 {
249 log<level::ERR>("System event sensor not found");
250 elog<InternalFailure>();
251 }
252 }
253 }
254
255 if (isFromSELLogger)
Tom Joseph6b7a1432017-05-19 10:43:36 +0530256 {
Lei YUaf378fa2020-12-02 16:28:57 +0800257 // It is expected to be a custom SEL entry
Lei YU07c15b72020-12-06 15:08:06 +0800258 auto recordType = static_cast<uint8_t>(convert(m[strRecordType]));
Lei YU5015e952020-12-03 14:01:31 +0800259 auto isOEM = isRecordOEM(recordType);
260 if (isOEM)
261 {
Lei YU07c15b72020-12-06 15:08:06 +0800262 constructOEMSEL(recordType, chronoTimeStamp, m, record);
Lei YU5015e952020-12-03 14:01:31 +0800263 }
264 else
265 {
Lei YU07c15b72020-12-06 15:08:06 +0800266 constructSEL(recordType, chronoTimeStamp, m, entryData, record);
Lei YU5015e952020-12-03 14:01:31 +0800267 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530268 }
269 else
270 {
Lei YUaf378fa2020-12-02 16:28:57 +0800271 record.event.eventRecord.timeStamp = static_cast<uint32_t>(
272 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
273 .count());
274
Lei YUaf378fa2020-12-02 16:28:57 +0800275 record.event.eventRecord.recordType = systemEventRecord;
276 record.event.eventRecord.generatorID = generatorID;
277 record.event.eventRecord.eventMsgRevision = eventMsgRevision;
278
279 record.event.eventRecord.sensorType = iter->second.sensorType;
280 record.event.eventRecord.sensorNum = iter->second.sensorID;
281 record.event.eventRecord.eventData1 = iter->second.eventOffset;
282
283 // Read Resolved from the log entry.
Lei YUaf378fa2020-12-02 16:28:57 +0800284 auto iterResolved = entryData.find(propResolved);
285 if (iterResolved == entryData.end())
286 {
287 log<level::ERR>("Error in reading Resolved field of logging entry");
288 elog<InternalFailure>();
289 }
290
Lei YUaf378fa2020-12-02 16:28:57 +0800291 // Evaluate if the event is assertion or deassertion event
292 if (std::get<bool>(iterResolved->second))
293 {
294 record.event.eventRecord.eventType =
295 deassertEvent | iter->second.eventReadingType;
296 }
297 else
298 {
299 record.event.eventRecord.eventType = iter->second.eventReadingType;
300 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530301 }
302
303 return record;
304}
305
306} // namespace internal
307
Tom Joseph6edc8a02017-06-30 18:52:56 +0530308GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
309{
310 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
311
Vernon Mauerya7f81cc2019-11-06 12:51:43 -0800312 static constexpr auto assocIntf =
313 "xyz.openbmc_project.Association.Definitions";
314 static constexpr auto assocProp = "Associations";
Tom Joseph6edc8a02017-06-30 18:52:56 +0530315
316 auto service = ipmi::getService(bus, assocIntf, objPath);
317
318 // Read the Associations interface.
Patrick Venture0b02be92018-08-31 11:55:55 -0700319 auto methodCall =
320 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
Tom Joseph6edc8a02017-06-30 18:52:56 +0530321 methodCall.append(assocIntf);
322 methodCall.append(assocProp);
323
324 auto reply = bus.call(methodCall);
325 if (reply.is_method_error())
326 {
327 log<level::ERR>("Error in reading Associations interface");
328 elog<InternalFailure>();
329 }
330
Patrick Venture0b02be92018-08-31 11:55:55 -0700331 using AssociationList =
332 std::vector<std::tuple<std::string, std::string, std::string>>;
Tom Joseph6edc8a02017-06-30 18:52:56 +0530333
Vernon Mauery16b86932019-05-01 08:36:11 -0700334 std::variant<AssociationList> list;
Tom Joseph6edc8a02017-06-30 18:52:56 +0530335 reply.read(list);
336
Vernon Maueryf442e112019-04-09 11:44:36 -0700337 auto& assocs = std::get<AssociationList>(list);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530338
339 /*
340 * Check if the log entry has any callout associations, if there is a
341 * callout association try to match the inventory path to the corresponding
342 * IPMI sensor.
343 */
344 for (const auto& item : assocs)
345 {
346 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
347 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700348 auto iter = invSensors.find(std::get<2>(item));
349 if (iter == invSensors.end())
350 {
351 iter = invSensors.find(BOARD_SENSOR);
352 if (iter == invSensors.end())
353 {
354 log<level::ERR>("Motherboard sensor not found");
355 elog<InternalFailure>();
356 }
357 }
Tom Joseph6edc8a02017-06-30 18:52:56 +0530358
Patrick Venture0b02be92018-08-31 11:55:55 -0700359 return internal::prepareSELEntry(objPath, iter);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530360 }
361 }
362
363 // If there are no callout associations link the log entry to system event
364 // sensor
365 auto iter = invSensors.find(SYSTEM_SENSOR);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530366 return internal::prepareSELEntry(objPath, iter);
367}
368
Tom Joseph399fd922017-06-30 18:40:30 +0530369std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
370{
371 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
372
373 auto service = ipmi::getService(bus, logEntryIntf, objPath);
374
375 using namespace std::string_literals;
376 static const auto propTimeStamp = "Timestamp"s;
377
Patrick Venture0b02be92018-08-31 11:55:55 -0700378 auto methodCall =
379 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
Tom Joseph399fd922017-06-30 18:40:30 +0530380 methodCall.append(logEntryIntf);
381 methodCall.append(propTimeStamp);
382
383 auto reply = bus.call(methodCall);
384 if (reply.is_method_error())
385 {
386 log<level::ERR>("Error in reading Timestamp from Entry interface");
387 elog<InternalFailure>();
388 }
389
Vernon Mauery16b86932019-05-01 08:36:11 -0700390 std::variant<uint64_t> timeStamp;
Tom Joseph399fd922017-06-30 18:40:30 +0530391 reply.read(timeStamp);
392
Vernon Maueryf442e112019-04-09 11:44:36 -0700393 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
Tom Joseph399fd922017-06-30 18:40:30 +0530394
395 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
396}
397
Tom Joseph232f5292017-07-07 20:14:02 +0530398void readLoggingObjectPaths(ObjectPaths& paths)
399{
400 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
401 auto depth = 0;
402 paths.clear();
403
Patrick Venture0b02be92018-08-31 11:55:55 -0700404 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
405 mapperIntf, "GetSubTreePaths");
Tom Joseph232f5292017-07-07 20:14:02 +0530406 mapperCall.append(logBasePath);
407 mapperCall.append(depth);
408 mapperCall.append(ObjectPaths({logEntryIntf}));
409
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300410 try
Tom Joseph232f5292017-07-07 20:14:02 +0530411 {
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300412 auto reply = bus.call(mapperCall);
Tom Joseph232f5292017-07-07 20:14:02 +0530413 reply.read(paths);
Tom Joseph232f5292017-07-07 20:14:02 +0530414 }
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300415 catch (const sdbusplus::exception::exception& e)
416 {
417 if (strcmp(e.name(),
418 "xyz.openbmc_project.Common.Error.ResourceNotFound"))
419 {
420 throw;
421 }
422 }
423
424 std::sort(paths.begin(), paths.end(),
425 [](const std::string& a, const std::string& b) {
426 namespace fs = std::filesystem;
427 fs::path pathA(a);
428 fs::path pathB(b);
429 auto idA = std::stoul(pathA.filename().string());
430 auto idB = std::stoul(pathB.filename().string());
431
432 return idA < idB;
433 });
Tom Joseph232f5292017-07-07 20:14:02 +0530434}
435
Tom Joseph6b7a1432017-05-19 10:43:36 +0530436} // namespace sel
437
438} // namespace ipmi