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     auto service = ipmi::getService(bus, assocIntf, objPath);
321 
322     // Read the Associations interface.
323     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
324                                           propIntf, "Get");
325     methodCall.append(assocIntf);
326     methodCall.append(assocProp);
327 
328     using AssociationList =
329         std::vector<std::tuple<std::string, std::string, std::string>>;
330 
331     std::variant<AssociationList> list;
332     try
333     {
334         auto reply = bus.call(methodCall);
335         reply.read(list);
336     }
337     catch (const std::exception& e)
338     {
339         log<level::ERR>("Error in reading Associations interface",
340                         entry("ERROR=%s", e.what()));
341         elog<InternalFailure>();
342     }
343 
344     auto& assocs = std::get<AssociationList>(list);
345 
346     /*
347      * Check if the log entry has any callout associations, if there is a
348      * callout association try to match the inventory path to the corresponding
349      * IPMI sensor.
350      */
351     for (const auto& item : assocs)
352     {
353         if (std::get<0>(item).compare(CALLOUT_FWD_ASSOCIATION) == 0)
354         {
355             auto iter = invSensors.find(std::get<2>(item));
356             if (iter == invSensors.end())
357             {
358                 iter = invSensors.find(BOARD_SENSOR);
359                 if (iter == invSensors.end())
360                 {
361                     log<level::ERR>("Motherboard sensor not found");
362                     elog<InternalFailure>();
363                 }
364             }
365 
366             return internal::prepareSELEntry(objPath, iter);
367         }
368     }
369 
370     // If there are no callout associations link the log entry to system event
371     // sensor
372     auto iter = invSensors.find(SYSTEM_SENSOR);
373     return internal::prepareSELEntry(objPath, iter);
374 }
375 
376 std::chrono::seconds getEntryTimeStamp(const std::string& objPath)
377 {
378     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
379 
380     auto service = ipmi::getService(bus, logEntryIntf, objPath);
381 
382     using namespace std::string_literals;
383     static const auto propTimeStamp = "Timestamp"s;
384 
385     auto methodCall = bus.new_method_call(service.c_str(), objPath.c_str(),
386                                           propIntf, "Get");
387     methodCall.append(logEntryIntf);
388     methodCall.append(propTimeStamp);
389 
390     std::variant<uint64_t> timeStamp;
391     try
392     {
393         auto reply = bus.call(methodCall);
394         reply.read(timeStamp);
395     }
396     catch (const std::exception& e)
397     {
398         log<level::ERR>("Error in reading Timestamp from Entry interface",
399                         entry("ERROR=%s", e.what()));
400         elog<InternalFailure>();
401     }
402 
403     std::chrono::milliseconds chronoTimeStamp(std::get<uint64_t>(timeStamp));
404 
405     return std::chrono::duration_cast<std::chrono::seconds>(chronoTimeStamp);
406 }
407 
408 void readLoggingObjectPaths(ObjectPaths& paths)
409 {
410     sdbusplus::bus_t bus{ipmid_get_sd_bus_connection()};
411     auto depth = 0;
412     paths.clear();
413 
414     auto mapperCall = bus.new_method_call(mapperBusName, mapperObjPath,
415                                           mapperIntf, "GetSubTreePaths");
416     mapperCall.append(logBasePath);
417     mapperCall.append(depth);
418     mapperCall.append(ObjectPaths({logEntryIntf}));
419 
420     try
421     {
422         auto reply = bus.call(mapperCall);
423         reply.read(paths);
424     }
425     catch (const sdbusplus::exception_t& e)
426     {
427         if (strcmp(e.name(),
428                    "xyz.openbmc_project.Common.Error.ResourceNotFound"))
429         {
430             throw;
431         }
432     }
433 
434     std::sort(paths.begin(), paths.end(),
435               [](const std::string& a, const std::string& b) {
436         namespace fs = std::filesystem;
437         fs::path pathA(a);
438         fs::path pathB(b);
439         auto idA = std::stoul(pathA.filename().string());
440         auto idB = std::stoul(pathB.filename().string());
441 
442         return idA < idB;
443     });
444 }
445 
446 } // namespace sel
447 
448 } // namespace ipmi
449