xref: /openbmc/phosphor-host-ipmid/selutility.cpp (revision 5015e955f41b0b01248c0ac2f9d378d25f858c12)
1 #include "config.h"
2 
3 #include "selutility.hpp"
4 
5 #include <charconv>
6 #include <chrono>
7 #include <filesystem>
8 #include <ipmid/api.hpp>
9 #include <ipmid/types.hpp>
10 #include <ipmid/utils.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <vector>
13 #include <xyz/openbmc_project/Common/error.hpp>
14 
15 extern const ipmi::sensor::InvObjectIDMap invSensors;
16 using namespace phosphor::logging;
17 using InternalFailure =
18     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
19 
20 namespace
21 {
22 
23 constexpr auto systemEventRecord = 0x02;
24 
25 } // namespace
26 
27 namespace ipmi
28 {
29 
30 namespace sel
31 {
32 
33 namespace internal
34 {
35 
36 inline bool isRecordOEM(uint8_t recordType)
37 {
38     return recordType != systemEventRecord;
39 }
40 
41 /** Parse the entry with format like key=val */
42 std::pair<std::string, std::string> parseEntry(const std::string& entry)
43 {
44     constexpr auto equalSign = "=";
45     auto pos = entry.find(equalSign);
46     assert(pos != std::string::npos);
47     auto key = entry.substr(0, pos);
48     auto val = entry.substr(pos + 1);
49     return {key, val};
50 }
51 
52 std::map<std::string, std::string>
53     parseAdditionalData(const AdditionalData& data)
54 {
55     std::map<std::string, std::string> ret;
56 
57     for (const auto& d : data)
58     {
59         ret.insert(parseEntry(d));
60     }
61     return ret;
62 }
63 
64 uint8_t convert(const std::string_view& str, int base = 10)
65 {
66     int ret;
67     std::from_chars(str.data(), str.data() + str.size(), ret, base);
68     return static_cast<uint8_t>(ret);
69 }
70 
71 // Convert the string to a vector of uint8_t, where the str is formatted as hex
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(convert(str.substr(i * 2, 2), 16));
80     }
81     return ret;
82 }
83 
84 GetSELEntryResponse
85     prepareSELEntry(const std::string& objPath,
86                     ipmi::sensor::InvObjectIDMap::const_iterator iter)
87 {
88     GetSELEntryResponse record{};
89 
90     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
91     auto service = ipmi::getService(bus, logEntryIntf, objPath);
92 
93     // Read all the log entry properties.
94     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
95                                           propIntf, "GetAll");
96     methodCall.append(logEntryIntf);
97 
98     auto reply = bus.call(methodCall);
99     if (reply.is_method_error())
100     {
101         log<level::ERR>("Error in reading logging property entries");
102         elog<InternalFailure>();
103     }
104 
105     std::map<PropertyName, PropertyType> entryData;
106     reply.read(entryData);
107 
108     // Read Id from the log entry.
109     static constexpr auto propId = "Id";
110     auto iterId = entryData.find(propId);
111     if (iterId == entryData.end())
112     {
113         log<level::ERR>("Error in reading Id of logging entry");
114         elog<InternalFailure>();
115     }
116 
117     // Read Timestamp from the log entry.
118     static constexpr auto propTimeStamp = "Timestamp";
119     auto iterTimeStamp = entryData.find(propTimeStamp);
120     if (iterTimeStamp == entryData.end())
121     {
122         log<level::ERR>("Error in reading Timestamp of logging entry");
123         elog<InternalFailure>();
124     }
125     std::chrono::milliseconds chronoTimeStamp(
126         std::get<uint64_t>(iterTimeStamp->second));
127 
128     if (iter == invSensors.end())
129     {
130         // It is expected to be a custom SEL entry
131         record.event.oemCD.recordID =
132             static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
133         static constexpr auto propAdditionalData = "AdditionalData";
134         // static constexpr auto strEventDir = "EVENT_DIR";
135         // static constexpr auto strGenerateId = "GENERATOR_ID";
136         static constexpr auto strRecordType = "RECORD_TYPE";
137         static constexpr auto strSensorData = "SENSOR_DATA";
138         // static constexpr auto strSensorPath = "SENSOR_PATH";
139         iterId = entryData.find(propAdditionalData);
140         if (iterId == entryData.end())
141         {
142             log<level::ERR>("Error finding AdditionalData");
143             elog<InternalFailure>();
144         }
145         const auto& addData = std::get<AdditionalData>(iterId->second);
146         auto m = parseAdditionalData(addData);
147         auto recordType = convert(m[strRecordType]);
148         auto isOEM = isRecordOEM(recordType);
149         if (isOEM)
150         {
151             if (recordType >= 0xC0 && recordType < 0xE0)
152             {
153                 record.event.oemCD.timeStamp = static_cast<uint32_t>(
154                     std::chrono::duration_cast<std::chrono::seconds>(
155                         chronoTimeStamp)
156                         .count());
157                 record.event.oemCD.recordType = recordType;
158                 // The ManufactureID and OEM Defined are packed in the sensor
159                 // data
160                 auto sensorData = convertVec(m[strSensorData]);
161                 // Fill the 9 bytes of Manufacture ID and oemDefined
162                 memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
163                        std::min(sensorData.size(), static_cast<size_t>(9)));
164             }
165             else if (recordType >= 0xE0)
166             {
167                 // TODO
168             }
169         }
170         else
171         {
172             // TODO
173         }
174     }
175     else
176     {
177 
178         record.event.eventRecord.recordID =
179             static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
180         record.event.eventRecord.timeStamp = static_cast<uint32_t>(
181             std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
182                 .count());
183 
184         static constexpr auto generatorID = 0x2000;
185         static constexpr auto eventMsgRevision = 0x04;
186 
187         record.event.eventRecord.recordType = systemEventRecord;
188         record.event.eventRecord.generatorID = generatorID;
189         record.event.eventRecord.eventMsgRevision = eventMsgRevision;
190 
191         record.event.eventRecord.sensorType = iter->second.sensorType;
192         record.event.eventRecord.sensorNum = iter->second.sensorID;
193         record.event.eventRecord.eventData1 = iter->second.eventOffset;
194 
195         // Read Resolved from the log entry.
196         static constexpr auto propResolved = "Resolved";
197         auto iterResolved = entryData.find(propResolved);
198         if (iterResolved == entryData.end())
199         {
200             log<level::ERR>("Error in reading Resolved field of logging entry");
201             elog<InternalFailure>();
202         }
203 
204         static constexpr auto deassertEvent = 0x80;
205 
206         // Evaluate if the event is assertion or deassertion event
207         if (std::get<bool>(iterResolved->second))
208         {
209             record.event.eventRecord.eventType =
210                 deassertEvent | iter->second.eventReadingType;
211         }
212         else
213         {
214             record.event.eventRecord.eventType = iter->second.eventReadingType;
215         }
216     }
217 
218     return record;
219 }
220 
221 } // namespace internal
222 
223 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
224 {
225     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
226 
227     static constexpr auto assocIntf =
228         "xyz.openbmc_project.Association.Definitions";
229     static constexpr auto assocProp = "Associations";
230 
231     auto service = ipmi::getService(bus, assocIntf, objPath);
232 
233     // Read the Associations interface.
234     auto methodCall =
235         bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
236     methodCall.append(assocIntf);
237     methodCall.append(assocProp);
238 
239     auto reply = bus.call(methodCall);
240     if (reply.is_method_error())
241     {
242         log<level::ERR>("Error in reading Associations interface");
243         elog<InternalFailure>();
244     }
245 
246     using AssociationList =
247         std::vector<std::tuple<std::string, std::string, std::string>>;
248 
249     std::variant<AssociationList> list;
250     reply.read(list);
251 
252     auto& assocs = std::get<AssociationList>(list);
253 
254     /*
255      * Check if the log entry has any callout associations, if there is a
256      * callout association try to match the inventory path to the corresponding
257      * IPMI sensor.
258      */
259     for (const auto& item : assocs)
260     {
261         if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
262         {
263             auto iter = invSensors.find(std::get<2>(item));
264             if (iter == invSensors.end())
265             {
266                 iter = invSensors.find(BOARD_SENSOR);
267                 if (iter == invSensors.end())
268                 {
269                     log<level::ERR>("Motherboard sensor not found");
270                     elog<InternalFailure>();
271                 }
272             }
273 
274             return internal::prepareSELEntry(objPath, iter);
275         }
276     }
277 
278     // If there are no callout associations link the log entry to system event
279     // sensor
280     auto iter = invSensors.find(SYSTEM_SENSOR);
281     return internal::prepareSELEntry(objPath, iter);
282 }
283 
284 std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
285 {
286     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
287 
288     auto service = ipmi::getService(bus, logEntryIntf, objPath);
289 
290     using namespace std::string_literals;
291     static const auto propTimeStamp = "Timestamp"s;
292 
293     auto methodCall =
294         bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
295     methodCall.append(logEntryIntf);
296     methodCall.append(propTimeStamp);
297 
298     auto reply = bus.call(methodCall);
299     if (reply.is_method_error())
300     {
301         log<level::ERR>("Error in reading Timestamp from Entry interface");
302         elog<InternalFailure>();
303     }
304 
305     std::variant<uint64_t> timeStamp;
306     reply.read(timeStamp);
307 
308     std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
309 
310     return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
311 }
312 
313 void readLoggingObjectPaths(ObjectPaths& paths)
314 {
315     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
316     auto depth = 0;
317     paths.clear();
318 
319     auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
320                                           mapperIntf, "GetSubTreePaths");
321     mapperCall.append(logBasePath);
322     mapperCall.append(depth);
323     mapperCall.append(ObjectPaths({logEntryIntf}));
324 
325     auto reply = bus.call(mapperCall);
326     if (reply.is_method_error())
327     {
328         log<level::INFO>("Error in reading logging entry object paths");
329     }
330     else
331     {
332         reply.read(paths);
333 
334         std::sort(paths.begin(), paths.end(),
335                   [](const std::string& a, const std::string& b) {
336                       namespace fs = std::filesystem;
337                       fs::path pathA(a);
338                       fs::path pathB(b);
339                       auto idA = std::stoul(pathA.filename().string());
340                       auto idB = std::stoul(pathB.filename().string());
341 
342                       return idA < idB;
343                   });
344     }
345 }
346 
347 } // namespace sel
348 
349 } // namespace ipmi
350