xref: /openbmc/phosphor-host-ipmid/selutility.cpp (revision 719a41cdf4cbcf5a46e6ed7f3da7f7852c665117)
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 constexpr auto propAdditionalData = "AdditionalData";
26 constexpr auto strEventDir = "EVENT_DIR";
27 constexpr auto strGenerateId = "GENERATOR_ID";
28 constexpr auto strRecordType = "RECORD_TYPE";
29 constexpr auto strSensorData = "SENSOR_DATA";
30 constexpr auto strSensorPath = "SENSOR_PATH";
31 
32 } // namespace
33 
34 namespace ipmi
35 {
36 
37 namespace sel
38 {
39 
40 namespace internal
41 {
42 
43 inline bool isRecordOEM(uint8_t recordType)
44 {
45     return recordType != systemEventRecord;
46 }
47 
48 using additionalDataMap = std::map<std::string, std::string>;
49 /** Parse the entry with format like key=val */
50 std::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 
60 additionalDataMap parseAdditionalData(const AdditionalData& data)
61 {
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 
71 uint8_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
79 std::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 
91 /** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
92 void 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 
118 GetSELEntryResponse
119     prepareSELEntry(const std::string& objPath,
120                     ipmi::sensor::InvObjectIDMap::const_iterator iter)
121 {
122     GetSELEntryResponse record{};
123 
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.
128     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
129                                           propIntf, "GetAll");
130     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 
139     std::map<PropertyName, PropertyType> entryData;
140     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 
151     // 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     }
159     std::chrono::milliseconds chronoTimeStamp(
160         std::get<uint64_t>(iterTimeStamp->second));
161 
162     if (iter == invSensors.end())
163     {
164         // It is expected to be a custom SEL entry
165         record.event.oemCD.recordID =
166             static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
167         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         {
179             constructOEMSel(recordType, chronoTimeStamp, m, record);
180         }
181         else
182         {
183             // TODO
184         }
185     }
186     else
187     {
188 
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 
195         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         }
227     }
228 
229     return record;
230 }
231 
232 } // namespace internal
233 
234 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
235 {
236     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
237 
238     static constexpr auto assocIntf =
239         "xyz.openbmc_project.Association.Definitions";
240     static constexpr auto assocProp = "Associations";
241 
242     auto service = ipmi::getService(bus, assocIntf, objPath);
243 
244     // Read the Associations interface.
245     auto methodCall =
246         bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
247     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 
257     using AssociationList =
258         std::vector<std::tuple<std::string, std::string, std::string>>;
259 
260     std::variant<AssociationList> list;
261     reply.read(list);
262 
263     auto& assocs = std::get<AssociationList>(list);
264 
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         {
274             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             }
284 
285             return internal::prepareSELEntry(objPath, iter);
286         }
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);
292     return internal::prepareSELEntry(objPath, iter);
293 }
294 
295 std::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 
304     auto methodCall =
305         bus.new_method_call(service.c_str(), objPath.c_str(), propIntf, "Get");
306     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 
316     std::variant<uint64_t> timeStamp;
317     reply.read(timeStamp);
318 
319     std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
320 
321     return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
322 }
323 
324 void readLoggingObjectPaths(ObjectPaths& paths)
325 {
326     sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()};
327     auto depth = 0;
328     paths.clear();
329 
330     auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
331                                           mapperIntf, "GetSubTreePaths");
332     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 
345         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());
352 
353                       return idA < idB;
354                   });
355     }
356 }
357 
358 } // namespace sel
359 
360 } // namespace ipmi
361