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