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