blob: 8d327d2b875d3c8854bc806281d766998e09cacc [file] [log] [blame]
Patrick Venture46470a32018-09-07 19:26:25 -07001#include "config.h"
2
3#include "selutility.hpp"
4
Vernon Mauerye08fbff2019-04-03 09:19:34 -07005#include <ipmid/api.hpp>
Vernon Mauery33250242019-03-12 16:49:26 -07006#include <ipmid/types.hpp>
Vernon Mauery6a98fe72019-03-11 15:57:48 -07007#include <ipmid/utils.hpp>
Patrick Venture3a5071a2018-09-12 13:27:42 -07008#include <phosphor-logging/elog-errors.hpp>
Patrick Venture3a5071a2018-09-12 13:27:42 -07009#include <xyz/openbmc_project/Common/error.hpp>
Patrick Venture46470a32018-09-07 19:26:25 -070010
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -050011#include <charconv>
12#include <chrono>
13#include <filesystem>
14#include <vector>
15
Tom Joseph6b7a1432017-05-19 10:43:36 +053016extern const ipmi::sensor::InvObjectIDMap invSensors;
17using namespace phosphor::logging;
18using InternalFailure =
Patrick Venture0b02be92018-08-31 11:55:55 -070019 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
Tom Joseph6b7a1432017-05-19 10:43:36 +053020
Lei YU5015e952020-12-03 14:01:31 +080021namespace
22{
23
24constexpr auto systemEventRecord = 0x02;
Lei YU07c15b72020-12-06 15:08:06 +080025constexpr auto generatorID = 0x2000;
26constexpr auto eventMsgRevision = 0x04;
27constexpr auto assertEvent = 0x00;
28constexpr auto deassertEvent = 0x80;
29constexpr auto selDataSize = 3;
30constexpr auto oemCDDataSize = 9;
31constexpr auto oemEFDataSize = 13;
Lei YU5015e952020-12-03 14:01:31 +080032
Lei YU719a41c2020-12-03 17:50:44 +080033constexpr auto propAdditionalData = "AdditionalData";
Lei YU07c15b72020-12-06 15:08:06 +080034constexpr auto propResolved = "Resolved";
35
Lei YU719a41c2020-12-03 17:50:44 +080036constexpr auto strEventDir = "EVENT_DIR";
37constexpr auto strGenerateId = "GENERATOR_ID";
38constexpr auto strRecordType = "RECORD_TYPE";
39constexpr auto strSensorData = "SENSOR_DATA";
40constexpr auto strSensorPath = "SENSOR_PATH";
41
Lei YU5015e952020-12-03 14:01:31 +080042} // namespace
43
Tom Joseph6b7a1432017-05-19 10:43:36 +053044namespace ipmi
45{
46
47namespace sel
48{
49
50namespace internal
51{
52
Lei YU5015e952020-12-03 14:01:31 +080053inline bool isRecordOEM(uint8_t recordType)
54{
55 return recordType != systemEventRecord;
56}
57
Lei YU719a41c2020-12-03 17:50:44 +080058using additionalDataMap = std::map<std::string, std::string>;
Lei YU07c15b72020-12-06 15:08:06 +080059using entryDataMap = std::map<PropertyName, PropertyType>;
Lei YU5015e952020-12-03 14:01:31 +080060/** Parse the entry with format like key=val */
61std::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
Lei YU719a41c2020-12-03 17:50:44 +080071additionalDataMap parseAdditionalData(const AdditionalData& data)
Lei YU5015e952020-12-03 14:01:31 +080072{
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
Lei YU07c15b72020-12-06 15:08:06 +080082int convert(const std::string_view& str, int base = 10)
Lei YU5015e952020-12-03 14:01:31 +080083{
84 int ret;
85 std::from_chars(str.data(), str.data() + str.size(), ret, base);
Lei YU07c15b72020-12-06 15:08:06 +080086 return ret;
Lei YU5015e952020-12-03 14:01:31 +080087}
88
89// Convert the string to a vector of uint8_t, where the str is formatted as hex
90std::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 {
Lei YU07c15b72020-12-06 15:08:06 +080097 ret.emplace_back(
98 static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
Lei YU5015e952020-12-03 14:01:31 +080099 }
100 return ret;
101}
102
Lei YU719a41c2020-12-03 17:50:44 +0800103/** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
Lei YU07c15b72020-12-06 15:08:06 +0800104void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
Lei YU719a41c2020-12-03 17:50:44 +0800105 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(),
Lei YU07c15b72020-12-06 15:08:06 +0800119 std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
Lei YU719a41c2020-12-03 17:50:44 +0800120 }
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(),
Lei YU07c15b72020-12-06 15:08:06 +0800126 std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
Lei YU719a41c2020-12-03 17:50:44 +0800127 }
128}
129
Lei YU07c15b72020-12-06 15:08:06 +0800130void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
Willy Tu11d68892022-01-20 10:37:34 -0800131 const additionalDataMap& m, const entryDataMap&,
Lei YU07c15b72020-12-06 15:08:06 +0800132 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
Patrick Venture0b02be92018-08-31 11:55:55 -0700179GetSELEntryResponse
180 prepareSELEntry(const std::string& objPath,
181 ipmi::sensor::InvObjectIDMap::const_iterator iter)
Tom Joseph6b7a1432017-05-19 10:43:36 +0530182{
Patrick Venture0b02be92018-08-31 11:55:55 -0700183 GetSELEntryResponse record{};
Tom Joseph6b7a1432017-05-19 10:43:36 +0530184
Patrick Williams5d82f472022-07-22 19:26:53 -0500185 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
Tom Joseph6b7a1432017-05-19 10:43:36 +0530186 auto service = ipmi::getService(bus, logEntryIntf, objPath);
187
188 // Read all the log entry properties.
Patrick Venture0b02be92018-08-31 11:55:55 -0700189 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
190 propIntf, "GetAll");
Tom Joseph6b7a1432017-05-19 10:43:36 +0530191 methodCall.append(logEntryIntf);
192
193 auto reply = bus.call(methodCall);
194 if (reply.is_method_error())
195 {
196 log<level::ERR>("Error in reading logging property entries");
197 elog<InternalFailure>();
198 }
199
Lei YU07c15b72020-12-06 15:08:06 +0800200 entryDataMap entryData;
Tom Joseph6b7a1432017-05-19 10:43:36 +0530201 reply.read(entryData);
202
203 // Read Id from the log entry.
204 static constexpr auto propId = "Id";
205 auto iterId = entryData.find(propId);
206 if (iterId == entryData.end())
207 {
208 log<level::ERR>("Error in reading Id of logging entry");
209 elog<InternalFailure>();
210 }
211
Tom Joseph6b7a1432017-05-19 10:43:36 +0530212 // Read Timestamp from the log entry.
213 static constexpr auto propTimeStamp = "Timestamp";
214 auto iterTimeStamp = entryData.find(propTimeStamp);
215 if (iterTimeStamp == entryData.end())
216 {
217 log<level::ERR>("Error in reading Timestamp of logging entry");
218 elog<InternalFailure>();
219 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530220 std::chrono::milliseconds chronoTimeStamp(
Vernon Maueryf442e112019-04-09 11:44:36 -0700221 std::get<uint64_t>(iterTimeStamp->second));
Tom Joseph6b7a1432017-05-19 10:43:36 +0530222
Lei YU690f4d52021-04-28 19:05:24 +0800223 bool isFromSELLogger = false;
224 additionalDataMap m;
225
226 // The recordID are with the same offset between different types,
227 // so we are safe to set the recordID here
228 record.event.eventRecord.recordID =
229 static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
230
231 iterId = entryData.find(propAdditionalData);
232 if (iterId != entryData.end())
233 {
234 // Check if it's a SEL from phosphor-sel-logger which shall contain
235 // the record ID, etc
236 const auto& addData = std::get<AdditionalData>(iterId->second);
237 m = parseAdditionalData(addData);
238 auto recordTypeIter = m.find(strRecordType);
239 if (recordTypeIter != m.end())
240 {
241 // It is a SEL from phosphor-sel-logger
242 isFromSELLogger = true;
243 }
244 else
245 {
246 // Not a SEL from phosphor-sel-logger, it shall have a valid
247 // invSensor
248 if (iter == invSensors.end())
249 {
250 log<level::ERR>("System event sensor not found");
251 elog<InternalFailure>();
252 }
253 }
254 }
255
256 if (isFromSELLogger)
Tom Joseph6b7a1432017-05-19 10:43:36 +0530257 {
Lei YUaf378fa2020-12-02 16:28:57 +0800258 // It is expected to be a custom SEL entry
Lei YU07c15b72020-12-06 15:08:06 +0800259 auto recordType = static_cast<uint8_t>(convert(m[strRecordType]));
Lei YU5015e952020-12-03 14:01:31 +0800260 auto isOEM = isRecordOEM(recordType);
261 if (isOEM)
262 {
Lei YU07c15b72020-12-06 15:08:06 +0800263 constructOEMSEL(recordType, chronoTimeStamp, m, record);
Lei YU5015e952020-12-03 14:01:31 +0800264 }
265 else
266 {
Lei YU07c15b72020-12-06 15:08:06 +0800267 constructSEL(recordType, chronoTimeStamp, m, entryData, record);
Lei YU5015e952020-12-03 14:01:31 +0800268 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530269 }
270 else
271 {
Lei YUaf378fa2020-12-02 16:28:57 +0800272 record.event.eventRecord.timeStamp = static_cast<uint32_t>(
273 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
274 .count());
275
Lei YUaf378fa2020-12-02 16:28:57 +0800276 record.event.eventRecord.recordType = systemEventRecord;
277 record.event.eventRecord.generatorID = generatorID;
278 record.event.eventRecord.eventMsgRevision = eventMsgRevision;
279
280 record.event.eventRecord.sensorType = iter->second.sensorType;
281 record.event.eventRecord.sensorNum = iter->second.sensorID;
282 record.event.eventRecord.eventData1 = iter->second.eventOffset;
283
284 // Read Resolved from the log entry.
Lei YUaf378fa2020-12-02 16:28:57 +0800285 auto iterResolved = entryData.find(propResolved);
286 if (iterResolved == entryData.end())
287 {
288 log<level::ERR>("Error in reading Resolved field of logging entry");
289 elog<InternalFailure>();
290 }
291
Lei YUaf378fa2020-12-02 16:28:57 +0800292 // Evaluate if the event is assertion or deassertion event
293 if (std::get<bool>(iterResolved->second))
294 {
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500295 record.event.eventRecord.eventType = deassertEvent |
296 iter->second.eventReadingType;
Lei YUaf378fa2020-12-02 16:28:57 +0800297 }
298 else
299 {
300 record.event.eventRecord.eventType = iter->second.eventReadingType;
301 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530302 }
303
304 return record;
305}
306
307} // namespace internal
308
Tom Joseph6edc8a02017-06-30 18:52:56 +0530309GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
310{
Patrick Williams5d82f472022-07-22 19:26:53 -0500311 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
Tom Joseph6edc8a02017-06-30 18:52:56 +0530312
Vernon Mauerya7f81cc2019-11-06 12:51:43 -0800313 static constexpr auto assocIntf =
314 "xyz.openbmc_project.Association.Definitions";
315 static constexpr auto assocProp = "Associations";
Tom Joseph6edc8a02017-06-30 18:52:56 +0530316
317 auto service = ipmi::getService(bus, assocIntf, objPath);
318
319 // Read the Associations interface.
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500320 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
321 propIntf, "Get");
Tom Joseph6edc8a02017-06-30 18:52:56 +0530322 methodCall.append(assocIntf);
323 methodCall.append(assocProp);
324
325 auto reply = bus.call(methodCall);
326 if (reply.is_method_error())
327 {
328 log<level::ERR>("Error in reading Associations interface");
329 elog<InternalFailure>();
330 }
331
Patrick Venture0b02be92018-08-31 11:55:55 -0700332 using AssociationList =
333 std::vector<std::tuple<std::string, std::string, std::string>>;
Tom Joseph6edc8a02017-06-30 18:52:56 +0530334
Vernon Mauery16b86932019-05-01 08:36:11 -0700335 std::variant<AssociationList> list;
Tom Joseph6edc8a02017-06-30 18:52:56 +0530336 reply.read(list);
337
Vernon Maueryf442e112019-04-09 11:44:36 -0700338 auto& assocs = std::get<AssociationList>(list);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530339
340 /*
341 * Check if the log entry has any callout associations, if there is a
342 * callout association try to match the inventory path to the corresponding
343 * IPMI sensor.
344 */
345 for (const auto& item : assocs)
346 {
347 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
348 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700349 auto iter = invSensors.find(std::get<2>(item));
350 if (iter == invSensors.end())
351 {
352 iter = invSensors.find(BOARD_SENSOR);
353 if (iter == invSensors.end())
354 {
355 log<level::ERR>("Motherboard sensor not found");
356 elog<InternalFailure>();
357 }
358 }
Tom Joseph6edc8a02017-06-30 18:52:56 +0530359
Patrick Venture0b02be92018-08-31 11:55:55 -0700360 return internal::prepareSELEntry(objPath, iter);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530361 }
362 }
363
364 // If there are no callout associations link the log entry to system event
365 // sensor
366 auto iter = invSensors.find(SYSTEM_SENSOR);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530367 return internal::prepareSELEntry(objPath, iter);
368}
369
Tom Joseph399fd922017-06-30 18:40:30 +0530370std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
371{
Patrick Williams5d82f472022-07-22 19:26:53 -0500372 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
Tom Joseph399fd922017-06-30 18:40:30 +0530373
374 auto service = ipmi::getService(bus, logEntryIntf, objPath);
375
376 using namespace std::string_literals;
377 static const auto propTimeStamp = "Timestamp"s;
378
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500379 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
380 propIntf, "Get");
Tom Joseph399fd922017-06-30 18:40:30 +0530381 methodCall.append(logEntryIntf);
382 methodCall.append(propTimeStamp);
383
384 auto reply = bus.call(methodCall);
385 if (reply.is_method_error())
386 {
387 log<level::ERR>("Error in reading Timestamp from Entry interface");
388 elog<InternalFailure>();
389 }
390
Vernon Mauery16b86932019-05-01 08:36:11 -0700391 std::variant<uint64_t> timeStamp;
Tom Joseph399fd922017-06-30 18:40:30 +0530392 reply.read(timeStamp);
393
Vernon Maueryf442e112019-04-09 11:44:36 -0700394 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
Tom Joseph399fd922017-06-30 18:40:30 +0530395
396 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
397}
398
Tom Joseph232f5292017-07-07 20:14:02 +0530399void readLoggingObjectPaths(ObjectPaths& paths)
400{
Patrick Williams5d82f472022-07-22 19:26:53 -0500401 sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
Tom Joseph232f5292017-07-07 20:14:02 +0530402 auto depth = 0;
403 paths.clear();
404
Patrick Venture0b02be92018-08-31 11:55:55 -0700405 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
406 mapperIntf, "GetSubTreePaths");
Tom Joseph232f5292017-07-07 20:14:02 +0530407 mapperCall.append(logBasePath);
408 mapperCall.append(depth);
409 mapperCall.append(ObjectPaths({logEntryIntf}));
410
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300411 try
Tom Joseph232f5292017-07-07 20:14:02 +0530412 {
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300413 auto reply = bus.call(mapperCall);
Tom Joseph232f5292017-07-07 20:14:02 +0530414 reply.read(paths);
Tom Joseph232f5292017-07-07 20:14:02 +0530415 }
Patrick Williams5d82f472022-07-22 19:26:53 -0500416 catch (const sdbusplus::exception_t& e)
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300417 {
418 if (strcmp(e.name(),
419 "xyz.openbmc_project.Common.Error.ResourceNotFound"))
420 {
421 throw;
422 }
423 }
424
425 std::sort(paths.begin(), paths.end(),
426 [](const std::string& a, const std::string& b) {
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500427 namespace fs = std::filesystem;
428 fs::path pathA(a);
429 fs::path pathB(b);
430 auto idA = std::stoul(pathA.filename().string());
431 auto idB = std::stoul(pathB.filename().string());
Konstantin Aladyshev49434b62022-01-10 16:58:47 +0300432
Patrick Williamsfbc6c9d2023-05-10 07:50:16 -0500433 return idA < idB;
434 });
Tom Joseph232f5292017-07-07 20:14:02 +0530435}
436
Tom Joseph6b7a1432017-05-19 10:43:36 +0530437} // namespace sel
438
439} // namespace ipmi