blob: d83aa166b7c87b5e39c48a686dd81a5468e03e88 [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;
24
Lei YU719a41c2020-12-03 17:50:44 +080025constexpr auto propAdditionalData = "AdditionalData";
26constexpr auto strEventDir = "EVENT_DIR";
27constexpr auto strGenerateId = "GENERATOR_ID";
28constexpr auto strRecordType = "RECORD_TYPE";
29constexpr auto strSensorData = "SENSOR_DATA";
30constexpr auto strSensorPath = "SENSOR_PATH";
31
Lei YU5015e952020-12-03 14:01:31 +080032} // namespace
33
Tom Joseph6b7a1432017-05-19 10:43:36 +053034namespace ipmi
35{
36
37namespace sel
38{
39
40namespace internal
41{
42
Lei YU5015e952020-12-03 14:01:31 +080043inline bool isRecordOEM(uint8_t recordType)
44{
45 return recordType != systemEventRecord;
46}
47
Lei YU719a41c2020-12-03 17:50:44 +080048using additionalDataMap = std::map<std::string, std::string>;
Lei YU5015e952020-12-03 14:01:31 +080049/** Parse the entry with format like key=val */
50std::pair<std::string, std::string> parseEntry(const std::string& entry)
51{
52 constexpr auto equalSign = "=";
53 auto pos = entry.find(equalSign);
54 assert(pos != std::string::npos);
55 auto key = entry.substr(0, pos);
56 auto val = entry.substr(pos + 1);
57 return {key, val};
58}
59
Lei YU719a41c2020-12-03 17:50:44 +080060additionalDataMap parseAdditionalData(const AdditionalData& data)
Lei YU5015e952020-12-03 14:01:31 +080061{
62 std::map<std::string, std::string> ret;
63
64 for (const auto& d : data)
65 {
66 ret.insert(parseEntry(d));
67 }
68 return ret;
69}
70
71uint8_t convert(const std::string_view& str, int base = 10)
72{
73 int ret;
74 std::from_chars(str.data(), str.data() + str.size(), ret, base);
75 return static_cast<uint8_t>(ret);
76}
77
78// Convert the string to a vector of uint8_t, where the str is formatted as hex
79std::vector<uint8_t> convertVec(const std::string_view& str)
80{
81 std::vector<uint8_t> ret;
82 auto len = str.size() / 2;
83 ret.reserve(len);
84 for (size_t i = 0; i < len; ++i)
85 {
86 ret.emplace_back(convert(str.substr(i * 2, 2), 16));
87 }
88 return ret;
89}
90
Lei YU719a41c2020-12-03 17:50:44 +080091/** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
92void constructOEMSel(uint8_t recordType, std::chrono::milliseconds timestamp,
93 const additionalDataMap& m, GetSELEntryResponse& record)
94{
95 auto dataIter = m.find(strSensorData);
96 assert(dataIter != m.end());
97 auto sensorData = convertVec(dataIter->second);
98 if (recordType >= 0xC0 && recordType < 0xE0)
99 {
100 record.event.oemCD.timeStamp = static_cast<uint32_t>(
101 std::chrono::duration_cast<std::chrono::seconds>(timestamp)
102 .count());
103 record.event.oemCD.recordType = recordType;
104 // The ManufactureID and OEM Defined are packed in the sensor data
105 // Fill the 9 bytes of Manufacture ID and oemDefined
106 memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
107 std::min(sensorData.size(), static_cast<size_t>(9)));
108 }
109 else if (recordType >= 0xE0)
110 {
111 record.event.oemEF.recordType = recordType;
112 // The remaining 13 bytes are the OEM Defined data
113 memcpy(&record.event.oemEF.oemDefined, sensorData.data(),
114 std::min(sensorData.size(), static_cast<size_t>(13)));
115 }
116}
117
Patrick Venture0b02be92018-08-31 11:55:55 -0700118GetSELEntryResponse
119 prepareSELEntry(const std::string& objPath,
120 ipmi::sensor::InvObjectIDMap::const_iterator iter)
Tom Joseph6b7a1432017-05-19 10:43:36 +0530121{
Patrick Venture0b02be92018-08-31 11:55:55 -0700122 GetSELEntryResponse record{};
Tom Joseph6b7a1432017-05-19 10:43:36 +0530123
124 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
125 auto service = ipmi::getService(bus, logEntryIntf, objPath);
126
127 // Read all the log entry properties.
Patrick Venture0b02be92018-08-31 11:55:55 -0700128 auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
129 propIntf, "GetAll");
Tom Joseph6b7a1432017-05-19 10:43:36 +0530130 methodCall.append(logEntryIntf);
131
132 auto reply = bus.call(methodCall);
133 if (reply.is_method_error())
134 {
135 log<level::ERR>("Error in reading logging property entries");
136 elog<InternalFailure>();
137 }
138
Tom Joseph306878b2017-07-10 19:30:54 +0530139 std::map<PropertyName, PropertyType> entryData;
Tom Joseph6b7a1432017-05-19 10:43:36 +0530140 reply.read(entryData);
141
142 // Read Id from the log entry.
143 static constexpr auto propId = "Id";
144 auto iterId = entryData.find(propId);
145 if (iterId == entryData.end())
146 {
147 log<level::ERR>("Error in reading Id of logging entry");
148 elog<InternalFailure>();
149 }
150
Tom Joseph6b7a1432017-05-19 10:43:36 +0530151 // Read Timestamp from the log entry.
152 static constexpr auto propTimeStamp = "Timestamp";
153 auto iterTimeStamp = entryData.find(propTimeStamp);
154 if (iterTimeStamp == entryData.end())
155 {
156 log<level::ERR>("Error in reading Timestamp of logging entry");
157 elog<InternalFailure>();
158 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530159 std::chrono::milliseconds chronoTimeStamp(
Vernon Maueryf442e112019-04-09 11:44:36 -0700160 std::get<uint64_t>(iterTimeStamp->second));
Tom Joseph6b7a1432017-05-19 10:43:36 +0530161
Lei YUaf378fa2020-12-02 16:28:57 +0800162 if (iter == invSensors.end())
Tom Joseph6b7a1432017-05-19 10:43:36 +0530163 {
Lei YUaf378fa2020-12-02 16:28:57 +0800164 // It is expected to be a custom SEL entry
Lei YU5015e952020-12-03 14:01:31 +0800165 record.event.oemCD.recordID =
166 static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
Lei YU5015e952020-12-03 14:01:31 +0800167 iterId = entryData.find(propAdditionalData);
168 if (iterId == entryData.end())
169 {
170 log<level::ERR>("Error finding AdditionalData");
171 elog<InternalFailure>();
172 }
173 const auto& addData = std::get<AdditionalData>(iterId->second);
174 auto m = parseAdditionalData(addData);
175 auto recordType = convert(m[strRecordType]);
176 auto isOEM = isRecordOEM(recordType);
177 if (isOEM)
178 {
Lei YU719a41c2020-12-03 17:50:44 +0800179 constructOEMSel(recordType, chronoTimeStamp, m, record);
Lei YU5015e952020-12-03 14:01:31 +0800180 }
181 else
182 {
183 // TODO
184 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530185 }
186 else
187 {
Lei YUaf378fa2020-12-02 16:28:57 +0800188
189 record.event.eventRecord.recordID =
190 static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
191 record.event.eventRecord.timeStamp = static_cast<uint32_t>(
192 std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
193 .count());
194
Lei YUaf378fa2020-12-02 16:28:57 +0800195 static constexpr auto generatorID = 0x2000;
196 static constexpr auto eventMsgRevision = 0x04;
197
198 record.event.eventRecord.recordType = systemEventRecord;
199 record.event.eventRecord.generatorID = generatorID;
200 record.event.eventRecord.eventMsgRevision = eventMsgRevision;
201
202 record.event.eventRecord.sensorType = iter->second.sensorType;
203 record.event.eventRecord.sensorNum = iter->second.sensorID;
204 record.event.eventRecord.eventData1 = iter->second.eventOffset;
205
206 // Read Resolved from the log entry.
207 static constexpr auto propResolved = "Resolved";
208 auto iterResolved = entryData.find(propResolved);
209 if (iterResolved == entryData.end())
210 {
211 log<level::ERR>("Error in reading Resolved field of logging entry");
212 elog<InternalFailure>();
213 }
214
215 static constexpr auto deassertEvent = 0x80;
216
217 // Evaluate if the event is assertion or deassertion event
218 if (std::get<bool>(iterResolved->second))
219 {
220 record.event.eventRecord.eventType =
221 deassertEvent | iter->second.eventReadingType;
222 }
223 else
224 {
225 record.event.eventRecord.eventType = iter->second.eventReadingType;
226 }
Tom Joseph6b7a1432017-05-19 10:43:36 +0530227 }
228
229 return record;
230}
231
232} // namespace internal
233
Tom Joseph6edc8a02017-06-30 18:52:56 +0530234GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
235{
236 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
237
Vernon Mauerya7f81cc2019-11-06 12:51:43 -0800238 static constexpr auto assocIntf =
239 "xyz.openbmc_project.Association.Definitions";
240 static constexpr auto assocProp = "Associations";
Tom Joseph6edc8a02017-06-30 18:52:56 +0530241
242 auto service = ipmi::getService(bus, assocIntf, objPath);
243
244 // Read the Associations interface.
Patrick Venture0b02be92018-08-31 11:55:55 -0700245 auto methodCall =
246 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
Tom Joseph6edc8a02017-06-30 18:52:56 +0530247 methodCall.append(assocIntf);
248 methodCall.append(assocProp);
249
250 auto reply = bus.call(methodCall);
251 if (reply.is_method_error())
252 {
253 log<level::ERR>("Error in reading Associations interface");
254 elog<InternalFailure>();
255 }
256
Patrick Venture0b02be92018-08-31 11:55:55 -0700257 using AssociationList =
258 std::vector<std::tuple<std::string, std::string, std::string>>;
Tom Joseph6edc8a02017-06-30 18:52:56 +0530259
Vernon Mauery16b86932019-05-01 08:36:11 -0700260 std::variant<AssociationList> list;
Tom Joseph6edc8a02017-06-30 18:52:56 +0530261 reply.read(list);
262
Vernon Maueryf442e112019-04-09 11:44:36 -0700263 auto& assocs = std::get<AssociationList>(list);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530264
265 /*
266 * Check if the log entry has any callout associations, if there is a
267 * callout association try to match the inventory path to the corresponding
268 * IPMI sensor.
269 */
270 for (const auto& item : assocs)
271 {
272 if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
273 {
Patrick Venture0b02be92018-08-31 11:55:55 -0700274 auto iter = invSensors.find(std::get<2>(item));
275 if (iter == invSensors.end())
276 {
277 iter = invSensors.find(BOARD_SENSOR);
278 if (iter == invSensors.end())
279 {
280 log<level::ERR>("Motherboard sensor not found");
281 elog<InternalFailure>();
282 }
283 }
Tom Joseph6edc8a02017-06-30 18:52:56 +0530284
Patrick Venture0b02be92018-08-31 11:55:55 -0700285 return internal::prepareSELEntry(objPath, iter);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530286 }
287 }
288
289 // If there are no callout associations link the log entry to system event
290 // sensor
291 auto iter = invSensors.find(SYSTEM_SENSOR);
Tom Joseph6edc8a02017-06-30 18:52:56 +0530292 return internal::prepareSELEntry(objPath, iter);
293}
294
Tom Joseph399fd922017-06-30 18:40:30 +0530295std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
296{
297 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
298
299 auto service = ipmi::getService(bus, logEntryIntf, objPath);
300
301 using namespace std::string_literals;
302 static const auto propTimeStamp = "Timestamp"s;
303
Patrick Venture0b02be92018-08-31 11:55:55 -0700304 auto methodCall =
305 bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
Tom Joseph399fd922017-06-30 18:40:30 +0530306 methodCall.append(logEntryIntf);
307 methodCall.append(propTimeStamp);
308
309 auto reply = bus.call(methodCall);
310 if (reply.is_method_error())
311 {
312 log<level::ERR>("Error in reading Timestamp from Entry interface");
313 elog<InternalFailure>();
314 }
315
Vernon Mauery16b86932019-05-01 08:36:11 -0700316 std::variant<uint64_t> timeStamp;
Tom Joseph399fd922017-06-30 18:40:30 +0530317 reply.read(timeStamp);
318
Vernon Maueryf442e112019-04-09 11:44:36 -0700319 std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
Tom Joseph399fd922017-06-30 18:40:30 +0530320
321 return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
322}
323
Tom Joseph232f5292017-07-07 20:14:02 +0530324void readLoggingObjectPaths(ObjectPaths& paths)
325{
326 sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
327 auto depth = 0;
328 paths.clear();
329
Patrick Venture0b02be92018-08-31 11:55:55 -0700330 auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
331 mapperIntf, "GetSubTreePaths");
Tom Joseph232f5292017-07-07 20:14:02 +0530332 mapperCall.append(logBasePath);
333 mapperCall.append(depth);
334 mapperCall.append(ObjectPaths({logEntryIntf}));
335
336 auto reply = bus.call(mapperCall);
337 if (reply.is_method_error())
338 {
339 log<level::INFO>("Error in reading logging entry object paths");
340 }
341 else
342 {
343 reply.read(paths);
344
Patrick Venture0b02be92018-08-31 11:55:55 -0700345 std::sort(paths.begin(), paths.end(),
346 [](const std::string& a, const std::string& b) {
347 namespace fs = std::filesystem;
348 fs::path pathA(a);
349 fs::path pathB(b);
350 auto idA = std::stoul(pathA.filename().string());
351 auto idB = std::stoul(pathB.filename().string());
Tom Joseph232f5292017-07-07 20:14:02 +0530352
Patrick Venture0b02be92018-08-31 11:55:55 -0700353 return idA < idB;
354 });
Tom Joseph232f5292017-07-07 20:14:02 +0530355 }
356}
357
Tom Joseph6b7a1432017-05-19 10:43:36 +0530358} // namespace sel
359
360} // namespace ipmi