xref: /openbmc/phosphor-host-ipmid/selutility.cpp (revision 4c52102525fa7478fe288ca3a930f6728ce324e5)
1 #include "config.h"
2 
3 #include "selutility.hpp"
4 
5 #include <ipmid/api.hpp>
6 #include <ipmid/types.hpp>
7 #include <ipmid/utils.hpp>
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <xyz/openbmc_project/Common/error.hpp>
10 
11 #include <charconv>
12 #include <chrono>
13 #include <filesystem>
14 #include <vector>
15 
16 extern const ipmi::sensor::InvObjectIDMap invSensors;
17 using namespace phosphor::logging;
18 using InternalFailure =
19     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
20 
21 namespace
22 {
23 
24 constexpr auto systemEventRecord = 0x02;
25 constexpr auto generatorID = 0x2000;
26 constexpr auto eventMsgRevision = 0x04;
27 constexpr auto assertEvent = 0x00;
28 constexpr auto deassertEvent = 0x80;
29 constexpr auto selDataSize = 3;
30 constexpr auto oemCDDataSize = 9;
31 constexpr auto oemEFDataSize = 13;
32 
33 constexpr auto propAdditionalData = "AdditionalData";
34 constexpr auto propResolved = "Resolved";
35 
36 constexpr auto strEventDir = "EVENT_DIR";
37 constexpr auto strGenerateId = "GENERATOR_ID";
38 constexpr auto strRecordType = "RECORD_TYPE";
39 constexpr auto strSensorData = "SENSOR_DATA";
40 constexpr auto strSensorPath = "SENSOR_PATH";
41 
42 } // namespace
43 
44 namespace ipmi
45 {
46 
47 namespace sel
48 {
49 
50 namespace internal
51 {
52 
53 inline bool isRecordOEM(uint8_t recordType)
54 {
55     return recordType != systemEventRecord;
56 }
57 
58 using additionalDataMap = std::map<std::string, std::string>;
59 using entryDataMap = std::map<PropertyName, PropertyType>;
60 /** Parse the entry with format like key=val */
61 std::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 
71 additionalDataMap parseAdditionalData(const AdditionalData& data)
72 {
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 
82 int convert(const std::string_view& str, int base = 10)
83 {
84     int ret = 0;
85     std::from_chars(str.data(), str.data() + str.size(), ret, base);
86     return ret;
87 }
88 
89 // Convert the string to a vector of uint8_t, where the str is formatted as hex
90 std::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     {
97         ret.emplace_back(
98             static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
99     }
100     return ret;
101 }
102 
103 /** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
104 void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
105                      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(),
119                std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
120     }
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(),
126                std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
127     }
128 }
129 
130 void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
131                   const additionalDataMap& m, const entryDataMap&,
132                   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 
179 GetSELEntryResponse
180     prepareSELEntry(const std::string& objPath,
181                     ipmi::sensor::InvObjectIDMap::const_iterator iter)
182 {
183     GetSELEntryResponse record{};
184 
185     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
186     auto service = ipmi::getService(bus, logEntryIntf, objPath);
187 
188     // Read all the log entry properties.
189     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
190                                           propIntf, "GetAll");
191     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 
200     entryDataMap entryData;
201     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 
212     // 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     }
220     std::chrono::milliseconds chronoTimeStamp(
221         std::get<uint64_t>(iterTimeStamp->second));
222 
223     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)
257     {
258         // It is expected to be a custom SEL entry
259         auto recordType = static_cast<uint8_t>(convert(m[strRecordType]));
260         auto isOEM = isRecordOEM(recordType);
261         if (isOEM)
262         {
263             constructOEMSEL(recordType, chronoTimeStamp, m, record);
264         }
265         else
266         {
267             constructSEL(recordType, chronoTimeStamp, m, entryData, record);
268         }
269     }
270     else
271     {
272         record.event.eventRecord.timeStamp = static_cast<uint32_t>(
273             std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
274                 .count());
275 
276         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.
285         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 
292         // Evaluate if the event is assertion or deassertion event
293         if (std::get<bool>(iterResolved->second))
294         {
295             record.event.eventRecord.eventType = deassertEvent |
296                                                  iter->second.eventReadingType;
297         }
298         else
299         {
300             record.event.eventRecord.eventType = iter->second.eventReadingType;
301         }
302     }
303 
304     return record;
305 }
306 
307 } // namespace internal
308 
309 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
310 {
311     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
312 
313     static constexpr auto assocIntf =
314         "xyz.openbmc_project.Association.Definitions";
315     static constexpr auto assocProp = "Associations";
316 
317     auto service = ipmi::getService(bus, assocIntf, objPath);
318 
319     // Read the Associations interface.
320     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
321                                           propIntf, "Get");
322     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 
332     using AssociationList =
333         std::vector<std::tuple<std::string, std::string, std::string>>;
334 
335     std::variant<AssociationList> list;
336     reply.read(list);
337 
338     auto& assocs = std::get<AssociationList>(list);
339 
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         {
349             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             }
359 
360             return internal::prepareSELEntry(objPath, iter);
361         }
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);
367     return internal::prepareSELEntry(objPath, iter);
368 }
369 
370 std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
371 {
372     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
373 
374     auto service = ipmi::getService(bus, logEntryIntf, objPath);
375 
376     using namespace std::string_literals;
377     static const auto propTimeStamp = "Timestamp"s;
378 
379     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
380                                           propIntf, "Get");
381     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 
391     std::variant<uint64_t> timeStamp;
392     reply.read(timeStamp);
393 
394     std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
395 
396     return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
397 }
398 
399 void readLoggingObjectPaths(ObjectPaths& paths)
400 {
401     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
402     auto depth = 0;
403     paths.clear();
404 
405     auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
406                                           mapperIntf, "GetSubTreePaths");
407     mapperCall.append(logBasePath);
408     mapperCall.append(depth);
409     mapperCall.append(ObjectPaths({logEntryIntf}));
410 
411     try
412     {
413         auto reply = bus.call(mapperCall);
414         reply.read(paths);
415     }
416     catch (const sdbusplus::exception_t& e)
417     {
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) {
427         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());
432 
433         return idA < idB;
434     });
435 }
436 
437 } // namespace sel
438 
439 } // namespace ipmi
440