xref: /openbmc/phosphor-host-ipmid/selutility.cpp (revision 93aa983e36a7140d594e175a00a5b3a0021146b8)
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 <phosphor-logging/lg2.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 #include <xyz/openbmc_project/ObjectMapper/common.hpp>
12 
13 #include <charconv>
14 #include <chrono>
15 #include <filesystem>
16 #include <vector>
17 
18 extern const ipmi::sensor::InvObjectIDMap invSensors;
19 using namespace phosphor::logging;
20 using InternalFailure =
21     sdbusplus::error::xyz::openbmc_project::common::InternalFailure;
22 
23 using ObjectMapper = sdbusplus::common::xyz::openbmc_project::ObjectMapper;
24 
25 namespace
26 {
27 
28 constexpr auto systemEventRecord = 0x02;
29 constexpr auto generatorID = 0x2000;
30 constexpr auto eventMsgRevision = 0x04;
31 constexpr auto assertEvent = 0x00;
32 constexpr auto deassertEvent = 0x80;
33 constexpr auto selDataSize = 3;
34 constexpr auto oemCDDataSize = 9;
35 constexpr auto oemEFDataSize = 13;
36 
37 constexpr auto propAdditionalData = "AdditionalData";
38 constexpr auto propResolved = "Resolved";
39 
40 constexpr auto strEventDir = "EVENT_DIR";
41 constexpr auto strGenerateId = "GENERATOR_ID";
42 constexpr auto strRecordType = "RECORD_TYPE";
43 constexpr auto strSensorData = "SENSOR_DATA";
44 constexpr auto strSensorPath = "SENSOR_PATH";
45 
46 } // namespace
47 
48 namespace ipmi
49 {
50 
51 namespace sel
52 {
53 
54 namespace internal
55 {
56 
isRecordOEM(uint8_t recordType)57 inline bool isRecordOEM(uint8_t recordType)
58 {
59     return recordType != systemEventRecord;
60 }
61 
62 using entryDataMap = std::map<PropertyName, PropertyType>;
63 
convert(const std::string_view & str,int base=10)64 int convert(const std::string_view& str, int base = 10)
65 {
66     int ret = 0;
67     std::from_chars(str.data(), str.data() + str.size(), ret, base);
68     return ret;
69 }
70 
71 // Convert the string to a vector of uint8_t, where the str is formatted as hex
convertVec(const std::string_view & str)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(
80             static_cast<uint8_t>(convert(str.substr(i * 2, 2), 16)));
81     }
82     return ret;
83 }
84 
85 /** Construct OEM SEL record according to IPMI spec 32.2, 32.3. */
constructOEMSEL(uint8_t recordType,std::chrono::milliseconds timestamp,const AdditionalData & data,GetSELEntryResponse & record)86 void constructOEMSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
87                      const AdditionalData& data, GetSELEntryResponse& record)
88 {
89     auto dataIter = data.find(strSensorData);
90     assert(dataIter != data.end());
91     auto sensorData = convertVec(dataIter->second);
92     if (recordType >= 0xC0 && recordType < 0xE0)
93     {
94         record.event.oemCD.timeStamp = static_cast<uint32_t>(
95             std::chrono::duration_cast<std::chrono::seconds>(timestamp)
96                 .count());
97         record.event.oemCD.recordType = recordType;
98         // The ManufactureID and OEM Defined are packed in the sensor data
99         // Fill the 9 bytes of Manufacture ID and oemDefined
100         memcpy(&record.event.oemCD.manufacturerID, sensorData.data(),
101                std::min(sensorData.size(), static_cast<size_t>(oemCDDataSize)));
102     }
103     else if (recordType >= 0xE0)
104     {
105         record.event.oemEF.recordType = recordType;
106         // The remaining 13 bytes are the OEM Defined data
107         memcpy(&record.event.oemEF.oemDefined, sensorData.data(),
108                std::min(sensorData.size(), static_cast<size_t>(oemEFDataSize)));
109     }
110 }
111 
constructSEL(uint8_t recordType,std::chrono::milliseconds timestamp,const AdditionalData & data,const entryDataMap &,GetSELEntryResponse & record)112 void constructSEL(uint8_t recordType, std::chrono::milliseconds timestamp,
113                   const AdditionalData& data, const entryDataMap&,
114                   GetSELEntryResponse& record)
115 {
116     if (recordType != systemEventRecord)
117     {
118         lg2::error("Invalid recordType");
119         elog<InternalFailure>();
120     }
121 
122     // Default values when there is no matched sensor
123     record.event.eventRecord.sensorType = 0;
124     record.event.eventRecord.sensorNum = 0xFF;
125     record.event.eventRecord.eventType = 0;
126 
127     auto iter = data.find(strSensorPath);
128     assert(iter != data.end());
129     const auto& sensorPath = iter->second;
130     auto sensorIter = invSensors.find(sensorPath);
131 
132     if (sensorIter != invSensors.end())
133     {
134         // There is a matched sensor
135         record.event.eventRecord.sensorType = sensorIter->second.sensorType;
136         record.event.eventRecord.sensorNum = sensorIter->second.sensorID;
137 
138         iter = data.find(strEventDir);
139         assert(iter != data.end());
140         auto eventDir = static_cast<uint8_t>(convert(iter->second));
141         uint8_t assert = eventDir ? assertEvent : deassertEvent;
142         record.event.eventRecord.eventType =
143             assert | sensorIter->second.eventReadingType;
144     }
145     record.event.eventRecord.recordType = recordType;
146     record.event.eventRecord.timeStamp = static_cast<uint32_t>(
147         std::chrono::duration_cast<std::chrono::seconds>(timestamp).count());
148     iter = data.find(strGenerateId);
149     assert(iter != data.end());
150     record.event.eventRecord.generatorID =
151         static_cast<uint16_t>(convert(iter->second));
152     record.event.eventRecord.eventMsgRevision = eventMsgRevision;
153     iter = data.find(strSensorData);
154     assert(iter != data.end());
155     auto sensorData = convertVec(iter->second);
156     // The remaining 3 bytes are the sensor data
157     memcpy(&record.event.eventRecord.eventData1, sensorData.data(),
158            std::min(sensorData.size(), static_cast<size_t>(selDataSize)));
159 }
160 
prepareSELEntry(const std::string & objPath,ipmi::sensor::InvObjectIDMap::const_iterator iter)161 GetSELEntryResponse prepareSELEntry(
162     const std::string& objPath,
163     ipmi::sensor::InvObjectIDMap::const_iterator iter)
164 {
165     GetSELEntryResponse record{};
166 
167     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
168     auto service = ipmi::getService(bus, logEntryIntf, objPath);
169 
170     // Read all the log entry properties.
171     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
172                                           propIntf, "GetAll");
173     methodCall.append(logEntryIntf);
174 
175     entryDataMap entryData;
176     try
177     {
178         auto reply = bus.call(methodCall);
179         reply.read(entryData);
180     }
181     catch (const std::exception& e)
182     {
183         lg2::error("Error in reading logging property entries: {ERROR}",
184                    "ERROR", e);
185         elog<InternalFailure>();
186     }
187 
188     // Read Id from the log entry.
189     static constexpr auto propId = "Id";
190     auto iterId = entryData.find(propId);
191     if (iterId == entryData.end())
192     {
193         lg2::error("Error in reading Id of logging entry");
194         elog<InternalFailure>();
195     }
196 
197     // Read Timestamp from the log entry.
198     static constexpr auto propTimeStamp = "Timestamp";
199     auto iterTimeStamp = entryData.find(propTimeStamp);
200     if (iterTimeStamp == entryData.end())
201     {
202         lg2::error("Error in reading Timestamp of logging entry");
203         elog<InternalFailure>();
204     }
205     std::chrono::milliseconds chronoTimeStamp(
206         std::get<uint64_t>(iterTimeStamp->second));
207 
208     bool isFromSELLogger = false;
209 
210     // The recordID are with the same offset between different types,
211     // so we are safe to set the recordID here
212     record.event.eventRecord.recordID =
213         static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
214 
215     iterId = entryData.find(propAdditionalData);
216     if (iterId != entryData.end())
217     {
218         // Check if it's a SEL from phosphor-sel-logger which shall contain
219         // the record ID, etc
220         const auto& addData = std::get<AdditionalData>(iterId->second);
221         auto recordTypeIter = addData.find(strRecordType);
222         if (recordTypeIter != addData.end())
223         {
224             // It is a SEL from phosphor-sel-logger
225             isFromSELLogger = true;
226         }
227         else
228         {
229             // Not a SEL from phosphor-sel-logger, it shall have a valid
230             // invSensor
231             if (iter == invSensors.end())
232             {
233                 lg2::error("System event sensor not found");
234                 elog<InternalFailure>();
235             }
236         }
237     }
238 
239     if (isFromSELLogger)
240     {
241         // It is expected to be a custom SEL entry
242         const auto& addData = std::get<AdditionalData>(iterId->second);
243         auto recordType =
244             static_cast<uint8_t>(convert(addData.find(strRecordType)->second));
245         auto isOEM = isRecordOEM(recordType);
246         if (isOEM)
247         {
248             constructOEMSEL(recordType, chronoTimeStamp, addData, record);
249         }
250         else
251         {
252             constructSEL(recordType, chronoTimeStamp, addData, entryData,
253                          record);
254         }
255     }
256     else
257     {
258         record.event.eventRecord.timeStamp = static_cast<uint32_t>(
259             std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
260                 .count());
261 
262         record.event.eventRecord.recordType = systemEventRecord;
263         record.event.eventRecord.generatorID = generatorID;
264         record.event.eventRecord.eventMsgRevision = eventMsgRevision;
265 
266         record.event.eventRecord.sensorType = iter->second.sensorType;
267         record.event.eventRecord.sensorNum = iter->second.sensorID;
268         record.event.eventRecord.eventData1 = iter->second.eventOffset;
269 
270         // Read Resolved from the log entry.
271         auto iterResolved = entryData.find(propResolved);
272         if (iterResolved == entryData.end())
273         {
274             lg2::error("Error in reading Resolved field of logging entry");
275             elog<InternalFailure>();
276         }
277 
278         // Evaluate if the event is assertion or deassertion event
279         if (std::get<bool>(iterResolved->second))
280         {
281             record.event.eventRecord.eventType =
282                 deassertEvent | iter->second.eventReadingType;
283         }
284         else
285         {
286             record.event.eventRecord.eventType = iter->second.eventReadingType;
287         }
288     }
289 
290     return record;
291 }
292 
293 } // namespace internal
294 
convertLogEntrytoSEL(const std::string & objPath)295 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
296 {
297     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
298 
299     static constexpr auto assocIntf =
300         "xyz.openbmc_project.Association.Definitions";
301     static constexpr auto assocProp = "Associations";
302 
303     std::vector<ipmi::Association> assocs;
304     try
305     {
306         auto service = ipmi::getService(bus, assocIntf, objPath);
307         auto propValue =
308             ipmi::getDbusProperty(bus, service, objPath, assocIntf, assocProp);
309         assocs = std::get<std::vector<ipmi::Association>>(propValue);
310     }
311     catch (const std::exception& e)
312     {
313         lg2::error("Error in reading Associations interface: {ERROR}", "ERROR",
314                    e);
315         elog<InternalFailure>();
316     }
317 
318     /*
319      * Check if the log entry has any callout associations, if there is a
320      * callout association try to match the inventory path to the corresponding
321      * IPMI sensor.
322      */
323     for (const auto& item : assocs)
324     {
325         if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
326         {
327             auto iter = invSensors.find(std::get<2>(item));
328             if (iter == invSensors.end())
329             {
330                 iter = invSensors.find(BOARD_SENSOR);
331                 if (iter == invSensors.end())
332                 {
333                     lg2::error("Motherboard sensor not found");
334                     elog<InternalFailure>();
335                 }
336             }
337 
338             return internal::prepareSELEntry(objPath, iter);
339         }
340     }
341 
342     // If there are no callout associations link the log entry to system event
343     // sensor
344     auto iter = invSensors.find(SYSTEM_SENSOR);
345     return internal::prepareSELEntry(objPath, iter);
346 }
347 
getEntryTimeStamp(const std::string & objPath)348 std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
349 {
350     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
351 
352     static constexpr auto propTimeStamp = "Timestamp";
353 
354     uint64_t timeStamp;
355     try
356     {
357         auto service = ipmi::getService(bus, logEntryIntf, objPath);
358         auto propValue = ipmi::getDbusProperty(bus, service, objPath,
359                                                logEntryIntf, propTimeStamp);
360         timeStamp = std::get<uint64_t>(propValue);
361     }
362     catch (const std::exception& e)
363     {
364         lg2::error("Error in reading Timestamp from Entry interface: {ERROR}",
365                    "ERROR", e);
366         elog<InternalFailure>();
367     }
368 
369     std::chrono::milliseconds chronoTimeStamp(timeStamp);
370 
371     return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
372 }
373 
readLoggingObjectPaths(ObjectPaths & paths)374 void readLoggingObjectPaths(ObjectPaths& paths)
375 {
376     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
377     auto depth = 0;
378     paths.clear();
379 
380     auto mapperCall = bus.new_method_call(
381         ObjectMapper::default_service, ObjectMapper::instance_path,
382         ObjectMapper::interface,
383         ObjectMapper::method_names::get_sub_tree_paths);
384     mapperCall.append(logBasePath);
385     mapperCall.append(depth);
386     mapperCall.append(ObjectPaths({logEntryIntf}));
387 
388     try
389     {
390         auto reply = bus.call(mapperCall);
391         reply.read(paths);
392     }
393     catch (const sdbusplus::exception_t& e)
394     {
395         if (strcmp(e.name(),
396                    "xyz.openbmc_project.Common.Error.ResourceNotFound"))
397         {
398             throw;
399         }
400     }
401 
402     std::sort(paths.begin(), paths.end(),
403               [](const std::string& a, const std::string& b) {
404                   namespace fs = std::filesystem;
405                   fs::path pathA(a);
406                   fs::path pathB(b);
407                   auto idA = std::stoul(pathA.filename().string());
408                   auto idB = std::stoul(pathB.filename().string());
409 
410                   return idA < idB;
411               });
412 }
413 
414 } // namespace sel
415 
416 } // namespace ipmi
417