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