xref: /openbmc/phosphor-logging/extensions/openpower-pels/manager.cpp (revision 40fb54935ce7367636a7156039396ee91cc4d5e2)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3 
4 #include "manager.hpp"
5 
6 #include "additional_data.hpp"
7 #include "elog_serialize.hpp"
8 #include "json_utils.hpp"
9 #include "pel.hpp"
10 #include "pel_entry.hpp"
11 #include "pel_values.hpp"
12 #include "service_indicators.hpp"
13 #include "severity.hpp"
14 
15 #include <sys/inotify.h>
16 #include <unistd.h>
17 
18 #include <phosphor-logging/lg2.hpp>
19 #include <xyz/openbmc_project/Common/error.hpp>
20 #include <xyz/openbmc_project/Logging/Create/server.hpp>
21 
22 #include <filesystem>
23 #include <format>
24 #include <fstream>
25 
26 namespace openpower
27 {
28 namespace pels
29 {
30 
31 using namespace phosphor::logging;
32 namespace fs = std::filesystem;
33 namespace rg = openpower::pels::message;
34 
35 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
36 
37 using Create = sdbusplus::server::xyz::openbmc_project::logging::Create;
38 
39 namespace additional_data
40 {
41 constexpr auto rawPEL = "RAWPEL";
42 constexpr auto esel = "ESEL";
43 constexpr auto error = "ERROR_NAME";
44 } // namespace additional_data
45 
46 constexpr auto defaultLogMessage = "xyz.openbmc_project.Logging.Error.Default";
47 constexpr uint32_t bmcThermalCompID = 0x2700;
48 constexpr uint32_t bmcFansCompID = 0x2800;
49 
~Manager()50 Manager::~Manager()
51 {
52     if (_pelFileDeleteFD != -1)
53     {
54         if (_pelFileDeleteWatchFD != -1)
55         {
56             inotify_rm_watch(_pelFileDeleteFD, _pelFileDeleteWatchFD);
57         }
58         close(_pelFileDeleteFD);
59     }
60 }
61 
create(const std::string & message,uint32_t obmcLogID,uint64_t timestamp,Entry::Level severity,const std::map<std::string,std::string> & additionalData,const std::vector<std::string> & associations,const FFDCEntries & ffdc)62 void Manager::create(const std::string& message, uint32_t obmcLogID,
63                      uint64_t timestamp, Entry::Level severity,
64                      const std::map<std::string, std::string>& additionalData,
65                      const std::vector<std::string>& associations,
66                      const FFDCEntries& ffdc)
67 {
68     AdditionalData ad{additionalData};
69 
70     // If a PEL was passed in via a filename or in an ESEL,
71     // use that.  Otherwise, create one.
72     auto rawPelPath = ad.getValue(additional_data::rawPEL);
73     if (rawPelPath)
74     {
75         addRawPEL(*rawPelPath, obmcLogID);
76     }
77     else
78     {
79         auto esel = ad.getValue(additional_data::esel);
80         if (esel)
81         {
82             addESELPEL(*esel, obmcLogID);
83         }
84         else
85         {
86             createPEL(message, obmcLogID, timestamp, severity, additionalData,
87                       associations, ffdc);
88         }
89     }
90 
91     setEntryPath(obmcLogID);
92     setServiceProviderNotifyFlag(obmcLogID);
93 }
94 
addRawPEL(const std::string & rawPelPath,uint32_t obmcLogID)95 void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
96 {
97     if (fs::exists(rawPelPath))
98     {
99         std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
100 
101         auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
102                                          std::istreambuf_iterator<char>());
103         if (file.fail())
104         {
105             lg2::error(
106                 "Filesystem error reading a raw PEL. File = {FILE}, obmcLogID = {LOGID}",
107                 "FILE", rawPelPath, "LOGID", obmcLogID);
108             // TODO, Decide what to do here. Maybe nothing.
109             return;
110         }
111 
112         file.close();
113 
114         addPEL(data, obmcLogID);
115 
116         std::error_code ec;
117         fs::remove(rawPelPath, ec);
118     }
119     else
120     {
121         lg2::error(
122             "Raw PEL file from BMC event log does not exit. File = {FILE}, obmcLogID = {LOGID}",
123             "FILE", rawPelPath, "LOGID", obmcLogID);
124     }
125 }
126 
addPEL(std::vector<uint8_t> & pelData,uint32_t obmcLogID)127 void Manager::addPEL(std::vector<uint8_t>& pelData, uint32_t obmcLogID)
128 {
129     auto pel = std::make_unique<openpower::pels::PEL>(pelData, obmcLogID);
130     if (pel->valid())
131     {
132         // PELs created by others still need this field set by us.
133         pel->setCommitTime();
134 
135         // Assign Id other than to Hostbot PEL
136         if ((pel->privateHeader()).creatorID() !=
137             static_cast<uint8_t>(CreatorID::hostboot))
138         {
139             pel->assignID();
140         }
141         else
142         {
143             const Repository::LogID id{Repository::LogID::Pel(pel->id())};
144             auto result = _repo.hasPEL(id);
145             if (result)
146             {
147                 lg2::warning(
148                     "Duplicate HostBoot PEL ID {ID} found, moving it to archive folder",
149                     "ID", lg2::hex, pel->id());
150 
151                 _repo.archivePEL(*pel);
152 
153                 // No need to keep around the openBMC event log entry
154                 scheduleObmcLogDelete(obmcLogID);
155                 return;
156             }
157         }
158 
159         // Update System Info to Extended User Data
160         pel->updateSysInfoInExtendedUserDataSection(*_dataIface);
161 
162         // Check for severity 0x51 and update boot progress SRC
163         updateProgressSRC(pel);
164 
165         try
166         {
167             lg2::debug("Adding external PEL {ID} (BMC ID {BMCID}) to repo",
168                        "ID", lg2::hex, pel->id(), "BMCID", obmcLogID);
169             _repo.add(pel);
170 
171             if (_repo.sizeWarning())
172             {
173                 scheduleRepoPrune();
174             }
175 
176             // Activate any resulting service indicators if necessary
177             auto policy = service_indicators::getPolicy(*_dataIface);
178             policy->activate(*pel);
179         }
180         catch (const std::exception& e)
181         {
182             // Probably a full or r/o filesystem, not much we can do.
183             lg2::error("Unable to add PEL {ID} to Repository", "ID", lg2::hex,
184                        pel->id());
185         }
186 
187         updateEventId(pel);
188         updateResolution(*pel);
189         serializeLogEntry(obmcLogID);
190         createPELEntry(obmcLogID);
191 
192         // Check if firmware should quiesce system due to error
193         checkPelAndQuiesce(pel);
194     }
195     else
196     {
197         lg2::error("Invalid PEL received from the host. BMC ID = {ID}", "ID",
198                    obmcLogID);
199 
200         AdditionalData ad;
201         ad.add("PLID", getNumberString("0x%08X", pel->plid()));
202         ad.add("OBMC_LOG_ID", std::to_string(obmcLogID));
203         ad.add("PEL_SIZE", std::to_string(pelData.size()));
204 
205         std::string asciiString;
206         auto src = pel->primarySRC();
207         if (src)
208         {
209             asciiString = (*src)->asciiString();
210         }
211 
212         ad.add("SRC", asciiString);
213 
214         _eventLogger.log("org.open_power.Logging.Error.BadHostPEL",
215                          Entry::Level::Error, ad);
216 
217         // Save it to a file for debug in the lab.  Just keep the latest.
218         // Not adding it to the PEL because it could already be max size
219         // and don't want to truncate an already invalid PEL.
220         std::ofstream pelFile{getPELRepoPath() / "badPEL"};
221         pelFile.write(reinterpret_cast<const char*>(pelData.data()),
222                       pelData.size());
223 
224         // No need to keep around the openBMC event log entry
225         scheduleObmcLogDelete(obmcLogID);
226     }
227 }
228 
addESELPEL(const std::string & esel,uint32_t obmcLogID)229 void Manager::addESELPEL(const std::string& esel, uint32_t obmcLogID)
230 {
231     std::vector<uint8_t> data;
232 
233     lg2::debug("Adding PEL from ESEL. BMC ID = {ID}", "ID", obmcLogID);
234 
235     try
236     {
237         data = eselToRawData(esel);
238     }
239     catch (const std::exception& e)
240     {
241         // Try to add it below anyway, so it follows the usual bad data path.
242         lg2::error("Problems converting ESEL string to a byte vector");
243     }
244 
245     addPEL(data, obmcLogID);
246 }
247 
eselToRawData(const std::string & esel)248 std::vector<uint8_t> Manager::eselToRawData(const std::string& esel)
249 {
250     std::vector<uint8_t> data;
251     std::string byteString;
252 
253     // As the eSEL string looks like: "50 48 00 ab ..." there are 3
254     // characters per raw byte, and since the actual PEL data starts
255     // at the 16th byte, the code will grab the PEL data starting at
256     // offset 48 in the string.
257     static constexpr size_t pelStart = 16 * 3;
258 
259     if (esel.size() <= pelStart)
260     {
261         lg2::error("ESEL data too short, length = {LEN}", "LEN", esel.size());
262         throw std::length_error("ESEL data too short");
263     }
264 
265     for (size_t i = pelStart; i < esel.size(); i += 3)
266     {
267         if (i + 1 < esel.size())
268         {
269             byteString = esel.substr(i, 2);
270             data.push_back(std::stoi(byteString, nullptr, 16));
271         }
272         else
273         {
274             lg2::error("ESEL data too short, length = {LEN}", "LEN",
275                        esel.size());
276             throw std::length_error("ESEL data too short");
277         }
278     }
279 
280     return data;
281 }
282 
erase(uint32_t obmcLogID)283 void Manager::erase(uint32_t obmcLogID)
284 {
285     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
286 
287     auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
288     _pelEntries.erase(path);
289     _repo.remove(id);
290 }
291 
getLogIDWithHwIsolation(std::vector<uint32_t> & idsWithHwIsoEntry)292 void Manager::getLogIDWithHwIsolation(std::vector<uint32_t>& idsWithHwIsoEntry)
293 {
294     idsWithHwIsoEntry = _dataIface->getLogIDWithHwIsolation();
295 }
296 
isDeleteProhibited(uint32_t obmcLogID)297 bool Manager::isDeleteProhibited(uint32_t obmcLogID)
298 {
299     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
300     if (!_repo.hasPEL(id))
301     {
302         return false;
303     }
304     auto entryPath{std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID)};
305     auto entry = _pelEntries.find(entryPath);
306     if (entry != _pelEntries.end())
307     {
308         if (entry->second->guard())
309         {
310             auto hwIsolationAssocPaths = _dataIface->getAssociatedPaths(
311                 entryPath += "/isolated_hw_entry", "/", 0,
312                 {"xyz.openbmc_project.HardwareIsolation.Entry"});
313             if (!hwIsolationAssocPaths.empty())
314             {
315                 return true;
316             }
317         }
318     }
319     return false;
320 }
321 
convertToPelFFDC(const FFDCEntries & ffdc)322 PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc)
323 {
324     PelFFDC pelFFDC;
325 
326     std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) {
327         PelFFDCfile pf;
328         pf.subType = std::get<ffdcSubtypePos>(f);
329         pf.version = std::get<ffdcVersionPos>(f);
330         pf.fd = std::get<ffdcFDPos>(f);
331 
332         switch (std::get<ffdcFormatPos>(f))
333         {
334             case Create::FFDCFormat::JSON:
335                 pf.format = UserDataFormat::json;
336                 break;
337             case Create::FFDCFormat::CBOR:
338                 pf.format = UserDataFormat::cbor;
339                 break;
340             case Create::FFDCFormat::Text:
341                 pf.format = UserDataFormat::text;
342                 break;
343             case Create::FFDCFormat::Custom:
344                 pf.format = UserDataFormat::custom;
345                 break;
346         }
347 
348         pelFFDC.push_back(pf);
349     });
350 
351     return pelFFDC;
352 }
353 
createPEL(const std::string & message,uint32_t obmcLogID,uint64_t timestamp,phosphor::logging::Entry::Level severity,const std::map<std::string,std::string> & additionalData,const std::vector<std::string> &,const FFDCEntries & ffdc)354 void Manager::createPEL(
355     const std::string& message, uint32_t obmcLogID, uint64_t timestamp,
356     phosphor::logging::Entry::Level severity,
357     const std::map<std::string, std::string>& additionalData,
358     const std::vector<std::string>& /*associations*/, const FFDCEntries& ffdc)
359 {
360     auto start = std::chrono::steady_clock::now();
361     auto entry = _registry.lookup(message, rg::LookupType::name);
362     auto pelFFDC = convertToPelFFDC(ffdc);
363     AdditionalData ad{additionalData};
364     std::string msg;
365 
366     if (!entry)
367     {
368         // Instead, get the default entry that means there is no
369         // other matching entry.  This error will still use the
370         // AdditionalData values of the original error, and this
371         // code will add the error message value that wasn't found
372         // to this AD.  This way, there will at least be a PEL,
373         // possibly with callouts, to allow users to debug the
374         // issue that caused the error even without its own PEL.
375         lg2::error("Event not found in PEL message registry: {MSG}", "MSG",
376                    message);
377 
378         entry = _registry.lookup(defaultLogMessage, rg::LookupType::name);
379         if (!entry)
380         {
381             lg2::error("Default event not found in PEL message registry");
382             return;
383         }
384 
385         ad.add(additional_data::error, message);
386     }
387 
388     auto pel = std::make_unique<openpower::pels::PEL>(
389         *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface,
390         *_journal);
391 
392     _repo.add(pel);
393 
394     if (_repo.sizeWarning())
395     {
396         scheduleRepoPrune();
397     }
398 
399     // Check for severity 0x51 and update boot progress SRC
400     updateProgressSRC(pel);
401 
402     // Activate any resulting service indicators if necessary
403     auto policy = service_indicators::getPolicy(*_dataIface);
404     policy->activate(*pel);
405 
406     updateDBusSeverity(*pel);
407     updateEventId(pel);
408     updateResolution(*pel);
409     serializeLogEntry(obmcLogID);
410     createPELEntry(obmcLogID);
411 
412     auto src = pel->primarySRC();
413     if (src)
414     {
415         auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
416             std::chrono::steady_clock::now() - start);
417 
418         auto asciiString = (*src)->asciiString();
419         while (asciiString.back() == ' ')
420         {
421             asciiString.pop_back();
422         }
423         lg2::info("Created PEL {ID} (BMC ID {BMCID}) with SRC {SRC}", "ID",
424                   lg2::hex, pel->id(), "BMCID", pel->obmcLogID(), "SRC",
425                   asciiString, "PEL_CREATE_DURATION", duration.count());
426     }
427 
428     // Check if firmware should quiesce system due to error
429     checkPelAndQuiesce(pel);
430 }
431 
getPEL(uint32_t pelID)432 sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
433 {
434     Repository::LogID id{Repository::LogID::Pel(pelID)};
435     std::optional<int> fd;
436 
437     lg2::debug("getPEL {ID}", "ID", lg2::hex, pelID);
438 
439     try
440     {
441         fd = _repo.getPELFD(id);
442     }
443     catch (const std::exception& e)
444     {
445         throw common_error::InternalFailure();
446     }
447 
448     if (!fd)
449     {
450         throw common_error::InvalidArgument();
451     }
452 
453     scheduleFDClose(*fd);
454 
455     return *fd;
456 }
457 
scheduleFDClose(int fd)458 void Manager::scheduleFDClose(int fd)
459 {
460     _fdCloserEventSource = std::make_unique<sdeventplus::source::Defer>(
461         _event, std::bind(std::mem_fn(&Manager::closeFD), this, fd,
462                           std::placeholders::_1));
463 }
464 
closeFD(int fd,sdeventplus::source::EventBase &)465 void Manager::closeFD(int fd, sdeventplus::source::EventBase& /*source*/)
466 {
467     close(fd);
468     _fdCloserEventSource.reset();
469 }
470 
getPELFromOBMCID(uint32_t obmcLogID)471 std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
472 {
473     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
474     std::optional<std::vector<uint8_t>> data;
475 
476     lg2::debug("getPELFromOBMCID  {BMCID}", "BMCID", obmcLogID);
477 
478     try
479     {
480         data = _repo.getPELData(id);
481     }
482     catch (const std::exception& e)
483     {
484         throw common_error::InternalFailure();
485     }
486 
487     if (!data)
488     {
489         throw common_error::InvalidArgument();
490     }
491 
492     return *data;
493 }
494 
hostAck(uint32_t pelID)495 void Manager::hostAck(uint32_t pelID)
496 {
497     Repository::LogID id{Repository::LogID::Pel(pelID)};
498 
499     lg2::debug("HostHack {ID}", "ID", lg2::hex, pelID);
500 
501     if (!_repo.hasPEL(id))
502     {
503         throw common_error::InvalidArgument();
504     }
505 
506     if (_hostNotifier)
507     {
508         _hostNotifier->ackPEL(pelID);
509     }
510 }
511 
hostReject(uint32_t pelID,RejectionReason reason)512 void Manager::hostReject(uint32_t pelID, RejectionReason reason)
513 {
514     Repository::LogID id{Repository::LogID::Pel(pelID)};
515 
516     lg2::debug("HostReject {ID}, reason = {REASON}", "ID", lg2::hex, pelID,
517                "REASON", reason);
518 
519     if (!_repo.hasPEL(id))
520     {
521         throw common_error::InvalidArgument();
522     }
523 
524     if (reason == RejectionReason::BadPEL)
525     {
526         AdditionalData data;
527         data.add("BAD_ID", getNumberString("0x%08X", pelID));
528         _eventLogger.log("org.open_power.Logging.Error.SentBadPELToHost",
529                          Entry::Level::Informational, data);
530         if (_hostNotifier)
531         {
532             _hostNotifier->setBadPEL(pelID);
533         }
534     }
535     else if ((reason == RejectionReason::HostFull) && _hostNotifier)
536     {
537         _hostNotifier->setHostFull(pelID);
538     }
539 }
540 
scheduleRepoPrune()541 void Manager::scheduleRepoPrune()
542 {
543     _repoPrunerEventSource = std::make_unique<sdeventplus::source::Defer>(
544         _event, std::bind(std::mem_fn(&Manager::pruneRepo), this,
545                           std::placeholders::_1));
546 }
547 
pruneRepo(sdeventplus::source::EventBase &)548 void Manager::pruneRepo(sdeventplus::source::EventBase& /*source*/)
549 {
550     auto idsWithHwIsoEntry = _dataIface->getLogIDWithHwIsolation();
551 
552     auto idsToDelete = _repo.prune(idsWithHwIsoEntry);
553 
554     // Remove the OpenBMC event logs for the PELs that were just removed.
555     std::for_each(idsToDelete.begin(), idsToDelete.end(),
556                   [this](auto id) { this->_logManager.erase(id); });
557 
558     _repoPrunerEventSource.reset();
559 }
560 
setupPELDeleteWatch()561 void Manager::setupPELDeleteWatch()
562 {
563     _pelFileDeleteFD = inotify_init1(IN_NONBLOCK);
564     if (-1 == _pelFileDeleteFD)
565     {
566         auto e = errno;
567         lg2::error("inotify_init1 failed with errno {ERRNO}", "ERRNO", e);
568         abort();
569     }
570 
571     _pelFileDeleteWatchFD = inotify_add_watch(
572         _pelFileDeleteFD, _repo.repoPath().c_str(), IN_DELETE);
573     if (-1 == _pelFileDeleteWatchFD)
574     {
575         auto e = errno;
576         lg2::error("inotify_add_watch failed with errno {ERRNO}", "ERRNO", e);
577         abort();
578     }
579 
580     _pelFileDeleteEventSource = std::make_unique<sdeventplus::source::IO>(
581         _event, _pelFileDeleteFD, EPOLLIN,
582         std::bind(std::mem_fn(&Manager::pelFileDeleted), this,
583                   std::placeholders::_1, std::placeholders::_2,
584                   std::placeholders::_3));
585 }
586 
pelFileDeleted(sdeventplus::source::IO &,int,uint32_t revents)587 void Manager::pelFileDeleted(sdeventplus::source::IO& /*io*/, int /*fd*/,
588                              uint32_t revents)
589 {
590     if (!(revents & EPOLLIN))
591     {
592         return;
593     }
594 
595     // An event for 1 PEL uses 48B. When all PELs are deleted at once,
596     // as many events as there is room for can be handled in one callback.
597     // A size of 2000 will allow 41 to be processed, with additional
598     // callbacks being needed to process the remaining ones.
599     std::array<uint8_t, 2000> data{};
600     auto bytesRead = read(_pelFileDeleteFD, data.data(), data.size());
601     if (bytesRead < 0)
602     {
603         auto e = errno;
604         lg2::error("Failed reading data from inotify event, errno = {ERRNO}",
605                    "ERRNO", e);
606         abort();
607     }
608 
609     auto offset = 0;
610     while (offset < bytesRead)
611     {
612         auto event = reinterpret_cast<inotify_event*>(&data[offset]);
613         if (event->mask & IN_DELETE)
614         {
615             std::string filename{event->name};
616 
617             // Get the PEL ID from the filename and tell the
618             // repo it's been removed, and then delete the BMC
619             // event log if it's there.
620             auto pos = filename.find_first_of('_');
621             if (pos != std::string::npos)
622             {
623                 try
624                 {
625                     auto idString = filename.substr(pos + 1);
626                     auto pelID = std::stoul(idString, nullptr, 16);
627 
628                     Repository::LogID id{Repository::LogID::Pel(pelID)};
629                     auto removedLogID = _repo.remove(id);
630                     if (removedLogID)
631                     {
632                         _logManager.erase(removedLogID->obmcID.id);
633                     }
634                 }
635                 catch (const std::exception& e)
636                 {
637                     lg2::info("Could not find PEL ID from its filename {NAME}",
638                               "NAME", filename);
639                 }
640             }
641         }
642 
643         offset += offsetof(inotify_event, name) + event->len;
644     }
645 }
646 
createPELWithFFDCFiles(std::string message,Entry::Level severity,std::map<std::string,std::string> additionalData,std::vector<std::tuple<sdbusplus::server::xyz::openbmc_project::logging::Create::FFDCFormat,uint8_t,uint8_t,sdbusplus::message::unix_fd>> fFDC)647 std::tuple<uint32_t, uint32_t> Manager::createPELWithFFDCFiles(
648     std::string message, Entry::Level severity,
649     std::map<std::string, std::string> additionalData,
650     std::vector<std::tuple<
651         sdbusplus::server::xyz::openbmc_project::logging::Create::FFDCFormat,
652         uint8_t, uint8_t, sdbusplus::message::unix_fd>>
653         fFDC)
654 {
655     _logManager.create(message, severity, additionalData, fFDC);
656 
657     return {_logManager.lastEntryID(), _repo.lastPelID()};
658 }
659 
getPELJSON(uint32_t obmcLogID)660 std::string Manager::getPELJSON(uint32_t obmcLogID)
661 {
662     // Throws InvalidArgument if not found
663     auto pelID = getPELIdFromBMCLogId(obmcLogID);
664 
665     auto cmd = std::format("/usr/bin/peltool -i {:#x}", pelID);
666 
667     FILE* pipe = popen(cmd.c_str(), "r");
668     if (!pipe)
669     {
670         lg2::error("Error running cmd: {CMD}", "CMD", cmd);
671         throw common_error::InternalFailure();
672     }
673 
674     std::string output;
675     std::array<char, 1024> buffer;
676     while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
677     {
678         output.append(buffer.data());
679     }
680 
681     int rc = pclose(pipe);
682     if (WEXITSTATUS(rc) != 0)
683     {
684         lg2::error("Error running cmd: {CMD}, rc = {RC}", "CMD", cmd, "RC", rc);
685         throw common_error::InternalFailure();
686     }
687 
688     return output;
689 }
690 
checkPelAndQuiesce(std::unique_ptr<openpower::pels::PEL> & pel)691 void Manager::checkPelAndQuiesce(std::unique_ptr<openpower::pels::PEL>& pel)
692 {
693     if ((pel->userHeader().severity() ==
694          static_cast<uint8_t>(SeverityType::nonError)) ||
695         (pel->userHeader().severity() ==
696          static_cast<uint8_t>(SeverityType::recovered)))
697     {
698         lg2::debug(
699             "PEL severity informational or recovered. no quiesce needed");
700         return;
701     }
702     if (!_logManager.isQuiesceOnErrorEnabled())
703     {
704         lg2::debug("QuiesceOnHwError not enabled, no quiesce needed");
705         return;
706     }
707 
708     CreatorID creatorID{pel->privateHeader().creatorID()};
709 
710     if ((creatorID != CreatorID::openBMC) &&
711         (creatorID != CreatorID::hostboot) &&
712         (creatorID != CreatorID::ioDrawer) && (creatorID != CreatorID::occ) &&
713         (creatorID != CreatorID::phyp))
714     {
715         return;
716     }
717 
718     // Now check if it has any type of callout
719     if (pel->isHwCalloutPresent())
720     {
721         lg2::info(
722             "QuiesceOnHwError enabled, PEL severity not nonError or recovered, "
723             "and callout is present");
724 
725         _logManager.quiesceOnError(pel->obmcLogID());
726     }
727 }
728 
getEventId(const openpower::pels::PEL & pel) const729 std::string Manager::getEventId(const openpower::pels::PEL& pel) const
730 {
731     std::string str;
732     auto src = pel.primarySRC();
733     if (src)
734     {
735         const auto& hexwords = (*src)->hexwordData();
736 
737         std::string refcode = (*src)->asciiString();
738         size_t pos = refcode.find_last_not_of(0x20);
739         if (pos != std::string::npos)
740         {
741             refcode.erase(pos + 1);
742         }
743         str = refcode;
744 
745         for (auto& value : hexwords)
746         {
747             str += " ";
748             str += getNumberString("%08X", value);
749         }
750     }
751     return sanitizeFieldForDBus(str);
752 }
753 
updateEventId(std::unique_ptr<openpower::pels::PEL> & pel)754 void Manager::updateEventId(std::unique_ptr<openpower::pels::PEL>& pel)
755 {
756     std::string eventIdStr = getEventId(*pel);
757 
758     auto entryN = _logManager.entries.find(pel->obmcLogID());
759     if (entryN != _logManager.entries.end())
760     {
761         entryN->second->eventId(eventIdStr, true);
762     }
763 }
764 
sanitizeFieldForDBus(std::string field)765 std::string Manager::sanitizeFieldForDBus(std::string field)
766 {
767     std::for_each(field.begin(), field.end(), [](char& ch) {
768         if (((ch < ' ') || (ch > '~')) && (ch != '\n') && (ch != '\t'))
769         {
770             ch = ' ';
771         }
772     });
773     return field;
774 }
775 
getResolution(const openpower::pels::PEL & pel) const776 std::string Manager::getResolution(const openpower::pels::PEL& pel) const
777 {
778     std::string str;
779     std::string resolution;
780     auto src = pel.primarySRC();
781     if (src)
782     {
783         // First extract the callout pointer and then go through
784         const auto& callouts = (*src)->callouts();
785         namespace pv = openpower::pels::pel_values;
786         // All PELs dont have callout, check before parsing callout data
787         if (callouts)
788         {
789             const auto& entries = callouts->callouts();
790             // Entry starts with index 1
791             uint8_t index = 1;
792             for (auto& entry : entries)
793             {
794                 resolution += std::to_string(index) + ". ";
795                 // Adding Location code to resolution
796                 if (!entry->locationCode().empty())
797                     resolution += "Location Code: " + entry->locationCode() +
798                                   ", ";
799                 if (entry->fruIdentity())
800                 {
801                     // Get priority and set the resolution string
802                     str = pv::getValue(entry->priority(),
803                                        pel_values::calloutPriorityValues,
804                                        pel_values::registryNamePos);
805                     str[0] = toupper(str[0]);
806                     resolution += "Priority: " + str + ", ";
807                     if (entry->fruIdentity()->getPN().has_value())
808                     {
809                         resolution +=
810                             "PN: " + entry->fruIdentity()->getPN().value() +
811                             ", ";
812                     }
813                     if (entry->fruIdentity()->getSN().has_value())
814                     {
815                         resolution +=
816                             "SN: " + entry->fruIdentity()->getSN().value() +
817                             ", ";
818                     }
819                     if (entry->fruIdentity()->getCCIN().has_value())
820                     {
821                         resolution +=
822                             "CCIN: " + entry->fruIdentity()->getCCIN().value() +
823                             ", ";
824                     }
825                     // Add the maintenance procedure
826                     if (entry->fruIdentity()->getMaintProc().has_value())
827                     {
828                         resolution +=
829                             "Procedure: " +
830                             entry->fruIdentity()->getMaintProc().value() + ", ";
831                     }
832                 }
833                 resolution.resize(resolution.size() - 2);
834                 resolution += "\n";
835                 index++;
836             }
837         }
838     }
839     return sanitizeFieldForDBus(resolution);
840 }
841 
updateResolution(const openpower::pels::PEL & pel)842 bool Manager::updateResolution(const openpower::pels::PEL& pel)
843 {
844     std::string callouts = getResolution(pel);
845     auto entryN = _logManager.entries.find(pel.obmcLogID());
846     if (entryN != _logManager.entries.end())
847     {
848         entryN->second->resolution(callouts, true);
849     }
850 
851     return false;
852 }
853 
serializeLogEntry(uint32_t obmcLogID)854 void Manager::serializeLogEntry(uint32_t obmcLogID)
855 {
856     auto entryN = _logManager.entries.find(obmcLogID);
857     if (entryN != _logManager.entries.end())
858     {
859         serialize(*entryN->second);
860     }
861 }
862 
updateDBusSeverity(const openpower::pels::PEL & pel)863 void Manager::updateDBusSeverity(const openpower::pels::PEL& pel)
864 {
865     // The final severity of the PEL may not agree with the
866     // original severity of the D-Bus event log.  Update the
867     // D-Bus property to match in some cases.  This is to
868     // ensure there isn't a Critical or Warning Redfish event
869     // log for an informational or recovered PEL (or vice versa).
870     // This doesn't make an explicit call to serialize the new
871     // event log property value because updateEventId() is called
872     // right after this and will do it.
873     auto sevType =
874         static_cast<SeverityType>(pel.userHeader().severity() & 0xF0);
875 
876     auto entryN = _logManager.entries.find(pel.obmcLogID());
877     if (entryN != _logManager.entries.end())
878     {
879         auto newSeverity =
880             fixupLogSeverity(entryN->second->severity(), sevType);
881         if (newSeverity)
882         {
883             lg2::info("Changing event log {ID} severity from {OLD} "
884                       "to {NEW} to match PEL",
885                       "ID", lg2::hex, entryN->second->id(), "OLD",
886                       Entry::convertLevelToString(entryN->second->severity()),
887                       "NEW", Entry::convertLevelToString(*newSeverity));
888 
889             entryN->second->severity(*newSeverity, true);
890         }
891     }
892 }
893 
setEntryPath(uint32_t obmcLogID)894 void Manager::setEntryPath(uint32_t obmcLogID)
895 {
896     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
897     if (auto attributes = _repo.getPELAttributes(id); attributes)
898     {
899         auto& attr = attributes.value().get();
900         auto entry = _logManager.entries.find(obmcLogID);
901         if (entry != _logManager.entries.end())
902         {
903             entry->second->path(attr.path, true);
904         }
905     }
906 }
907 
setServiceProviderNotifyFlag(uint32_t obmcLogID)908 void Manager::setServiceProviderNotifyFlag(uint32_t obmcLogID)
909 {
910     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
911     if (auto attributes = _repo.getPELAttributes(id); attributes)
912     {
913         auto& attr = attributes.value().get();
914         auto entry = _logManager.entries.find(obmcLogID);
915         if (entry != _logManager.entries.end())
916         {
917             if (attr.actionFlags.test(callHomeFlagBit))
918             {
919                 entry->second->serviceProviderNotify(Entry::Notify::Notify,
920                                                      true);
921             }
922             else
923             {
924                 entry->second->serviceProviderNotify(Entry::Notify::Inhibit,
925                                                      true);
926             }
927         }
928     }
929 }
930 
createPELEntry(uint32_t obmcLogID,bool skipIaSignal)931 void Manager::createPELEntry(uint32_t obmcLogID, bool skipIaSignal)
932 {
933     std::map<std::string, PropertiesVariant> varData;
934     Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
935     if (auto attributes = _repo.getPELAttributes(id); attributes)
936     {
937         namespace pv = openpower::pels::pel_values;
938         auto& attr = attributes.value().get();
939 
940         // get the hidden flag values
941         auto sevType = static_cast<SeverityType>(attr.severity & 0xF0);
942         auto isHidden = true;
943         if (((sevType != SeverityType::nonError) &&
944              attr.actionFlags.test(reportFlagBit) &&
945              !attr.actionFlags.test(hiddenFlagBit)) ||
946             ((sevType == SeverityType::nonError) &&
947              attr.actionFlags.test(serviceActionFlagBit)))
948         {
949             isHidden = false;
950         }
951         varData.emplace(std::string("Hidden"), isHidden);
952         varData.emplace(
953             std::string("Subsystem"),
954             pv::getValue(attr.subsystem, pel_values::subsystemValues));
955 
956         varData.emplace(
957             std::string("ManagementSystemAck"),
958             (attr.hmcState == TransmissionState::acked ? true : false));
959 
960         varData.emplace("PlatformLogID", attr.plid);
961         varData.emplace("Deconfig", attr.deconfig);
962         varData.emplace("Guard", attr.guard);
963         varData.emplace("Timestamp", attr.creationTime);
964 
965         // Path to create PELEntry Interface is same as PEL
966         auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
967         // Create Interface for PELEntry and set properties
968         auto pelEntry = std::make_unique<PELEntry>(_logManager.getBus(), path,
969                                                    varData, obmcLogID, &_repo);
970         if (!skipIaSignal)
971         {
972             pelEntry->emit_added();
973         }
974         _pelEntries.emplace(std::move(path), std::move(pelEntry));
975     }
976 }
977 
getPELIdFromBMCLogId(uint32_t bmcLogId)978 uint32_t Manager::getPELIdFromBMCLogId(uint32_t bmcLogId)
979 {
980     Repository::LogID id{Repository::LogID::Obmc(bmcLogId)};
981     if (auto logId = _repo.getLogID(id); !logId.has_value())
982     {
983         throw common_error::InvalidArgument();
984     }
985     else
986     {
987         return logId->pelID.id;
988     }
989 }
990 
getBMCLogIdFromPELId(uint32_t pelId)991 uint32_t Manager::getBMCLogIdFromPELId(uint32_t pelId)
992 {
993     Repository::LogID id{Repository::LogID::Pel(pelId)};
994     if (auto logId = _repo.getLogID(id); !logId.has_value())
995     {
996         throw common_error::InvalidArgument();
997     }
998     else
999     {
1000         return logId->obmcID.id;
1001     }
1002 }
1003 
updateProgressSRC(std::unique_ptr<openpower::pels::PEL> & pel) const1004 void Manager::updateProgressSRC(
1005     std::unique_ptr<openpower::pels::PEL>& pel) const
1006 {
1007     const size_t refcodeBegin = 40;
1008     const size_t refcodeSize = 8;
1009 
1010     // Check for pel severity of type - 0x51 = critical error, system
1011     // termination
1012     if (pel->userHeader().severity() == 0x51)
1013     {
1014         auto src = pel->primarySRC();
1015         if (src)
1016         {
1017             std::vector<uint8_t> asciiSRC = (*src)->getSrcStruct();
1018 
1019             if (asciiSRC.size() < (refcodeBegin + refcodeSize))
1020             {
1021                 lg2::error(
1022                     "SRC struct is too short to get progress code ({SIZE})",
1023                     "SIZE", asciiSRC.size());
1024                 return;
1025             }
1026 
1027             // Pull the ASCII SRC from offset [40-47] e.g. BD8D1001
1028             std::vector<uint8_t> srcRefCode(
1029                 asciiSRC.begin() + refcodeBegin,
1030                 asciiSRC.begin() + refcodeBegin + refcodeSize);
1031 
1032             try
1033             {
1034                 _dataIface->createProgressSRC(srcRefCode, asciiSRC);
1035             }
1036             catch (const std::exception&)
1037             {
1038                 // Exception - may be no boot progress interface on dbus
1039             }
1040         }
1041     }
1042 }
1043 
scheduleObmcLogDelete(uint32_t obmcLogID)1044 void Manager::scheduleObmcLogDelete(uint32_t obmcLogID)
1045 {
1046     _obmcLogDeleteEventSource = std::make_unique<sdeventplus::source::Defer>(
1047         _event, std::bind(std::mem_fn(&Manager::deleteObmcLog), this,
1048                           std::placeholders::_1, obmcLogID));
1049 }
1050 
deleteObmcLog(sdeventplus::source::EventBase &,uint32_t obmcLogID)1051 void Manager::deleteObmcLog(sdeventplus::source::EventBase&, uint32_t obmcLogID)
1052 {
1053     lg2::info("Removing event log with no PEL: {BMCID}", "BMCID", obmcLogID);
1054     _logManager.erase(obmcLogID);
1055     _obmcLogDeleteEventSource.reset();
1056 }
1057 
clearPowerThermalDeconfigFlag(const std::string & locationCode,openpower::pels::PEL & pel)1058 bool Manager::clearPowerThermalDeconfigFlag(const std::string& locationCode,
1059                                             openpower::pels::PEL& pel)
1060 {
1061     // The requirements state that only power-thermal or
1062     // fan PELs need their deconfig flag cleared.
1063     static const std::vector<uint32_t> compIDs{bmcThermalCompID, bmcFansCompID};
1064 
1065     if (std::find(compIDs.begin(), compIDs.end(),
1066                   pel.privateHeader().header().componentID) == compIDs.end())
1067     {
1068         return false;
1069     }
1070 
1071     auto src = pel.primarySRC();
1072     const auto& callouts = (*src)->callouts();
1073     if (!callouts)
1074     {
1075         return false;
1076     }
1077 
1078     for (const auto& callout : callouts->callouts())
1079     {
1080         // Look for the passed in location code in a callout that
1081         // is either a normal HW callout or a symbolic FRU with
1082         // a trusted location code callout.
1083         if ((callout->locationCode() != locationCode) ||
1084             !callout->fruIdentity())
1085         {
1086             continue;
1087         }
1088 
1089         if ((callout->fruIdentity()->failingComponentType() !=
1090              src::FRUIdentity::hardwareFRU) &&
1091             (callout->fruIdentity()->failingComponentType() !=
1092              src::FRUIdentity::symbolicFRUTrustedLocCode))
1093         {
1094             continue;
1095         }
1096 
1097         lg2::info(
1098             "Clearing deconfig flag in PEL {ID} with SRC {SRC} because {LOC} was replaced",
1099             "ID", lg2::hex, pel.id(), "SRC", (*src)->asciiString().substr(0, 8),
1100             "LOC", locationCode);
1101         (*src)->clearErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured);
1102         return true;
1103     }
1104     return false;
1105 }
1106 
hardwarePresent(const std::string & locationCode)1107 void Manager::hardwarePresent(const std::string& locationCode)
1108 {
1109     Repository::PELUpdateFunc handlePowerThermalHardwarePresent =
1110         [locationCode](openpower::pels::PEL& pel) {
1111             return Manager::clearPowerThermalDeconfigFlag(locationCode, pel);
1112         };
1113 
1114     // If the PEL was created by the BMC and has the deconfig flag set,
1115     // it's a candidate to have the deconfig flag cleared.
1116     for (const auto& [id, attributes] : _repo.getAttributesMap())
1117     {
1118         if ((attributes.creator == static_cast<uint8_t>(CreatorID::openBMC)) &&
1119             attributes.deconfig)
1120         {
1121             auto updated = _repo.updatePEL(attributes.path,
1122                                            handlePowerThermalHardwarePresent);
1123 
1124             if (updated)
1125             {
1126                 // Also update the property on D-Bus
1127                 auto objPath = std::string(OBJ_ENTRY) + '/' +
1128                                std::to_string(id.obmcID.id);
1129                 auto entryN = _pelEntries.find(objPath);
1130                 if (entryN != _pelEntries.end())
1131                 {
1132                     entryN->second->deconfig(false);
1133                 }
1134                 else
1135                 {
1136                     lg2::error(
1137                         "Could not find PEL Entry D-Bus object for {PATH}",
1138                         "PATH", objPath);
1139                 }
1140             }
1141         }
1142     }
1143 }
1144 
1145 } // namespace pels
1146 } // namespace openpower
1147