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