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::error::xyz::openbmc_project::common::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     entryDataMap entryData;
194     try
195     {
196         auto reply = bus.call(methodCall);
197         reply.read(entryData);
198     }
199     catch (const std::exception& e)
200     {
201         log<level::ERR>("Error in reading logging property entries",
202                         entry("ERROR=%s", e.what()));
203         elog<InternalFailure>();
204     }
205 
206     // Read Id from the log entry.
207     static constexpr auto propId = "Id";
208     auto iterId = entryData.find(propId);
209     if (iterId == entryData.end())
210     {
211         log<level::ERR>("Error in reading Id of logging entry");
212         elog<InternalFailure>();
213     }
214 
215     // Read Timestamp from the log entry.
216     static constexpr auto propTimeStamp = "Timestamp";
217     auto iterTimeStamp = entryData.find(propTimeStamp);
218     if (iterTimeStamp == entryData.end())
219     {
220         log<level::ERR>("Error in reading Timestamp of logging entry");
221         elog<InternalFailure>();
222     }
223     std::chrono::milliseconds chronoTimeStamp(
224         std::get<uint64_t>(iterTimeStamp->second));
225 
226     bool isFromSELLogger = false;
227     additionalDataMap m;
228 
229     // The recordID are with the same offset between different types,
230     // so we are safe to set the recordID here
231     record.event.eventRecord.recordID =
232         static_cast<uint16_t>(std::get<uint32_t>(iterId->second));
233 
234     iterId = entryData.find(propAdditionalData);
235     if (iterId != entryData.end())
236     {
237         // Check if it's a SEL from phosphor-sel-logger which shall contain
238         // the record ID, etc
239         const auto& addData = std::get<AdditionalData>(iterId->second);
240         m = parseAdditionalData(addData);
241         auto recordTypeIter = m.find(strRecordType);
242         if (recordTypeIter != m.end())
243         {
244             // It is a SEL from phosphor-sel-logger
245             isFromSELLogger = true;
246         }
247         else
248         {
249             // Not a SEL from phosphor-sel-logger, it shall have a valid
250             // invSensor
251             if (iter == invSensors.end())
252             {
253                 log<level::ERR>("System event sensor not found");
254                 elog<InternalFailure>();
255             }
256         }
257     }
258 
259     if (isFromSELLogger)
260     {
261         // It is expected to be a custom SEL entry
262         auto recordType = static_cast<uint8_t>(convert(m[strRecordType]));
263         auto isOEM = isRecordOEM(recordType);
264         if (isOEM)
265         {
266             constructOEMSEL(recordType, chronoTimeStamp, m, record);
267         }
268         else
269         {
270             constructSEL(recordType, chronoTimeStamp, m, entryData, record);
271         }
272     }
273     else
274     {
275         record.event.eventRecord.timeStamp = static_cast<uint32_t>(
276             std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp)
277                 .count());
278 
279         record.event.eventRecord.recordType = systemEventRecord;
280         record.event.eventRecord.generatorID = generatorID;
281         record.event.eventRecord.eventMsgRevision = eventMsgRevision;
282 
283         record.event.eventRecord.sensorType = iter->second.sensorType;
284         record.event.eventRecord.sensorNum = iter->second.sensorID;
285         record.event.eventRecord.eventData1 = iter->second.eventOffset;
286 
287         // Read Resolved from the log entry.
288         auto iterResolved = entryData.find(propResolved);
289         if (iterResolved == entryData.end())
290         {
291             log<level::ERR>("Error in reading Resolved field of logging entry");
292             elog<InternalFailure>();
293         }
294 
295         // Evaluate if the event is assertion or deassertion event
296         if (std::get<bool>(iterResolved->second))
297         {
298             record.event.eventRecord.eventType = deassertEvent |
299                                                  iter->second.eventReadingType;
300         }
301         else
302         {
303             record.event.eventRecord.eventType = iter->second.eventReadingType;
304         }
305     }
306 
307     return record;
308 }
309 
310 } // namespace internal
311 
312 GetSELEntryResponse convertLogEntrytoSEL(const std::string& objPath)
313 {
314     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
315 
316     static constexpr auto assocIntf =
317         "xyz.openbmc_project.Association.Definitions";
318     static constexpr auto assocProp = "Associations";
319 
320     std::vector<ipmi::Association> assocs;
321     try
322     {
323         auto service = ipmi::getService(bus, assocIntf, objPath);
324         auto propValue = ipmi::getDbusProperty(bus, service, objPath, assocIntf,
325                                                assocProp);
326         assocs = std::get<std::vector<ipmi::Association>>(propValue);
327     }
328     catch (const std::exception& e)
329     {
330         log<level::ERR>("Error in reading Associations interface",
331                         entry("ERROR=%s", e.what()));
332         elog<InternalFailure>();
333     }
334 
335     /*
336      * Check if the log entry has any callout associations, if there is a
337      * callout association try to match the inventory path to the corresponding
338      * IPMI sensor.
339      */
340     for (const auto& item : assocs)
341     {
342         if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
343         {
344             auto iter = invSensors.find(std::get<2>(item));
345             if (iter == invSensors.end())
346             {
347                 iter = invSensors.find(BOARD_SENSOR);
348                 if (iter == invSensors.end())
349                 {
350                     log<level::ERR>("Motherboard sensor not found");
351                     elog<InternalFailure>();
352                 }
353             }
354 
355             return internal::prepareSELEntry(objPath, iter);
356         }
357     }
358 
359     // If there are no callout associations link the log entry to system event
360     // sensor
361     auto iter = invSensors.find(SYSTEM_SENSOR);
362     return internal::prepareSELEntry(objPath, iter);
363 }
364 
365 std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
366 {
367     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
368 
369     static constexpr auto propTimeStamp = "Timestamp";
370 
371     uint64_t timeStamp;
372     try
373     {
374         auto service = ipmi::getService(bus, logEntryIntf, objPath);
375         auto propValue = ipmi::getDbusProperty(bus, service, objPath,
376                                                logEntryIntf, propTimeStamp);
377         timeStamp = std::get<uint64_t>(propValue);
378     }
379     catch (const std::exception& e)
380     {
381         log<level::ERR>("Error in reading Timestamp from Entry interface",
382                         entry("ERROR=%s", e.what()));
383         elog<InternalFailure>();
384     }
385 
386     std::chrono::milliseconds chronoTimeStamp(timeStamp);
387 
388     return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
389 }
390 
391 void readLoggingObjectPaths(ObjectPaths& paths)
392 {
393     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
394     auto depth = 0;
395     paths.clear();
396 
397     auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
398                                           mapperIntf, "GetSubTreePaths");
399     mapperCall.append(logBasePath);
400     mapperCall.append(depth);
401     mapperCall.append(ObjectPaths({logEntryIntf}));
402 
403     try
404     {
405         auto reply = bus.call(mapperCall);
406         reply.read(paths);
407     }
408     catch (const sdbusplus::exception_t& e)
409     {
410         if (strcmp(e.name(),
411                    "xyz.openbmc_project.Common.Error.ResourceNotFound"))
412         {
413             throw;
414         }
415     }
416 
417     std::sort(paths.begin(), paths.end(),
418               [](const std::string& a, const std::string& b) {
419         namespace fs = std::filesystem;
420         fs::path pathA(a);
421         fs::path pathB(b);
422         auto idA = std::stoul(pathA.filename().string());
423         auto idB = std::stoul(pathB.filename().string());
424 
425         return idA < idB;
426     });
427 }
428 
429 } // namespace sel
430 
431 } // namespace ipmi
432