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