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