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