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 "config.h" 17 18 #include "pel.hpp" 19 20 #include "bcd_time.hpp" 21 #include "extended_user_data.hpp" 22 #include "extended_user_header.hpp" 23 #include "failing_mtms.hpp" 24 #include "fru_identity.hpp" 25 #include "json_utils.hpp" 26 #include "log_id.hpp" 27 #include "pel_rules.hpp" 28 #include "pel_values.hpp" 29 #include "section_factory.hpp" 30 #include "src.hpp" 31 #include "stream.hpp" 32 #include "user_data_formats.hpp" 33 34 #ifdef PEL_ENABLE_PHAL 35 #include "phal_service_actions.hpp" 36 #include "sbe_ffdc_handler.hpp" 37 #endif 38 39 #include <sys/stat.h> 40 #include <unistd.h> 41 42 #include <phosphor-logging/lg2.hpp> 43 44 #include <format> 45 #include <iostream> 46 #include <ranges> 47 48 namespace openpower 49 { 50 namespace pels 51 { 52 namespace pv = openpower::pels::pel_values; 53 54 constexpr auto unknownValue = "Unknown"; 55 constexpr auto AdDIMMInfoFetchError = "DIMMs Info Fetch Error"; 56 57 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp, 58 phosphor::logging::Entry::Level severity, 59 const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn, 60 const DataInterfaceBase& dataIface, const JournalBase& journal) 61 { 62 // No changes in input, for non SBE error related requests 63 PelFFDC ffdcFiles = ffdcFilesIn; 64 65 #ifdef PEL_ENABLE_PHAL 66 // Add sbe ffdc processed data into ffdcfiles. 67 namespace sbe = openpower::pels::sbe; 68 auto processReq = 69 std::any_of(ffdcFiles.begin(), ffdcFiles.end(), [](const auto& file) { 70 return file.format == UserDataFormat::custom && 71 file.subType == sbe::sbeFFDCSubType; 72 }); 73 // sbeFFDC can't be destroyed until the end of the PEL constructor 74 // because it needs to keep around the FFDC Files to be used below. 75 std::unique_ptr<sbe::SbeFFDC> sbeFFDCPtr; 76 if (processReq) 77 { 78 sbeFFDCPtr = 79 std::make_unique<sbe::SbeFFDC>(additionalData, ffdcFilesIn); 80 const auto& sbeFFDCFiles = sbeFFDCPtr->getSbeFFDC(); 81 ffdcFiles.insert(ffdcFiles.end(), sbeFFDCFiles.begin(), 82 sbeFFDCFiles.end()); 83 84 // update pel priority for spare clock failures 85 if (auto customSeverity = sbeFFDCPtr->getSeverity()) 86 { 87 severity = customSeverity.value(); 88 } 89 } 90 #endif 91 92 DebugData debugData; 93 nlohmann::json callouts; 94 95 _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID, 96 timestamp); 97 _uh = std::make_unique<UserHeader>(regEntry, severity, additionalData, 98 dataIface); 99 100 // Extract any callouts embedded in an FFDC file. 101 if (!ffdcFiles.empty()) 102 { 103 try 104 { 105 callouts = getCalloutJSON(ffdcFiles); 106 } 107 catch (const std::exception& e) 108 { 109 debugData.emplace("FFDC file JSON callouts error", 110 std::vector<std::string>{e.what()}); 111 } 112 } 113 114 auto src = 115 std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface); 116 117 nlohmann::json adSysInfoData(nlohmann::json::value_t::object); 118 addAdDetailsForDIMMsCallout(src, dataIface, adSysInfoData, debugData); 119 120 if (!src->getDebugData().empty()) 121 { 122 // Something didn't go as planned 123 debugData.emplace("SRC", src->getDebugData()); 124 } 125 126 auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src); 127 128 _optionalSections.push_back(std::move(src)); 129 _optionalSections.push_back(std::move(euh)); 130 131 auto mtms = std::make_unique<FailingMTMS>(dataIface); 132 _optionalSections.push_back(std::move(mtms)); 133 134 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface, true, 135 adSysInfoData); 136 addUserDataSection(std::move(ud)); 137 138 // Check for pel severity of type - 0x51 = critical error, system 139 // termination and update terminate bit in SRC for pels 140 updateTerminateBitInSRCSection(); 141 142 // Create a UserData section from AdditionalData. 143 if (!additionalData.empty()) 144 { 145 ud = util::makeADUserDataSection(additionalData); 146 addUserDataSection(std::move(ud)); 147 } 148 149 // Add any FFDC files into UserData sections 150 for (const auto& file : ffdcFiles) 151 { 152 ud = util::makeFFDCuserDataSection(regEntry.componentID, file); 153 if (!ud) 154 { 155 // Add this error into the debug data UserData section 156 std::ostringstream msg; 157 msg << "Could not make PEL FFDC UserData section from file" 158 << std::hex << regEntry.componentID << " " << file.subType 159 << " " << file.version; 160 if (debugData.count("FFDC File")) 161 { 162 debugData.at("FFDC File").push_back(msg.str()); 163 } 164 else 165 { 166 debugData.emplace("FFDC File", 167 std::vector<std::string>{msg.str()}); 168 } 169 170 continue; 171 } 172 173 addUserDataSection(std::move(ud)); 174 } 175 176 #ifdef PEL_ENABLE_PHAL 177 auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID); 178 openpower::pels::phal::createServiceActions(callouts, path, dataIface, 179 plid()); 180 #endif 181 182 // Store in the PEL any important debug data created while 183 // building the PEL sections. 184 if (!debugData.empty()) 185 { 186 nlohmann::json data; 187 data["PEL Internal Debug Data"] = debugData; 188 ud = util::makeJSONUserDataSection(data); 189 190 addUserDataSection(std::move(ud)); 191 192 // Also put in the journal for debug 193 for (const auto& [name, msgs] : debugData) 194 { 195 for (const auto& message : msgs) 196 { 197 lg2::info("{NAME}: {MSG}", "NAME", name, "MSG", message); 198 } 199 } 200 } 201 202 addJournalSections(regEntry, journal); 203 204 _ph->setSectionCount(2 + _optionalSections.size()); 205 206 checkRulesAndFix(); 207 } 208 209 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0) {} 210 211 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID) 212 { 213 populateFromRawData(data, obmcLogID); 214 } 215 216 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID) 217 { 218 Stream pelData{data}; 219 _ph = std::make_unique<PrivateHeader>(pelData); 220 if (obmcLogID != 0) 221 { 222 _ph->setOBMCLogID(obmcLogID); 223 } 224 225 _uh = std::make_unique<UserHeader>(pelData); 226 227 // Use the section factory to create the rest of the objects 228 for (size_t i = 2; i < _ph->sectionCount(); i++) 229 { 230 auto section = section_factory::create(pelData); 231 _optionalSections.push_back(std::move(section)); 232 } 233 } 234 235 bool PEL::valid() const 236 { 237 bool valid = _ph->valid(); 238 239 if (valid) 240 { 241 valid = _uh->valid(); 242 } 243 244 if (valid) 245 { 246 if (!std::all_of(_optionalSections.begin(), _optionalSections.end(), 247 [](const auto& section) { return section->valid(); })) 248 { 249 valid = false; 250 } 251 } 252 253 return valid; 254 } 255 256 void PEL::setCommitTime() 257 { 258 auto now = std::chrono::system_clock::now(); 259 _ph->setCommitTimestamp(getBCDTime(now)); 260 } 261 262 void PEL::assignID() 263 { 264 _ph->setID(generatePELID()); 265 } 266 267 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const 268 { 269 Stream pelData{pelBuffer}; 270 271 if (!valid()) 272 { 273 lg2::warning("Unflattening an invalid PEL"); 274 } 275 276 _ph->flatten(pelData); 277 _uh->flatten(pelData); 278 279 for (auto& section : _optionalSections) 280 { 281 section->flatten(pelData); 282 } 283 } 284 285 std::vector<uint8_t> PEL::data() const 286 { 287 std::vector<uint8_t> pelData; 288 flatten(pelData); 289 return pelData; 290 } 291 292 size_t PEL::size() const 293 { 294 size_t size = 0; 295 296 if (_ph) 297 { 298 size += _ph->header().size; 299 } 300 301 if (_uh) 302 { 303 size += _uh->header().size; 304 } 305 306 for (const auto& section : _optionalSections) 307 { 308 size += section->header().size; 309 } 310 311 return size; 312 } 313 314 std::optional<SRC*> PEL::primarySRC() const 315 { 316 auto src = std::find_if( 317 _optionalSections.begin(), _optionalSections.end(), [](auto& section) { 318 return section->header().id == 319 static_cast<uint16_t>(SectionID::primarySRC); 320 }); 321 if (src != _optionalSections.end()) 322 { 323 return static_cast<SRC*>(src->get()); 324 } 325 326 return std::nullopt; 327 } 328 329 void PEL::checkRulesAndFix() 330 { 331 // Only fix if the action flags are at their default value which 332 // means they weren't specified in the registry. Otherwise 333 // assume the user knows what they are doing. 334 if (_uh->actionFlags() == actionFlagsDefault) 335 { 336 auto [actionFlags, eventType] = 337 pel_rules::check(0, _uh->eventType(), _uh->severity()); 338 339 _uh->setActionFlags(actionFlags); 340 _uh->setEventType(eventType); 341 } 342 } 343 344 void PEL::printSectionInJSON( 345 const Section& section, std::string& buf, 346 std::map<uint16_t, size_t>& pluralSections, message::Registry& registry, 347 const std::vector<std::string>& plugins, uint8_t creatorID) const 348 { 349 char tmpB[5]; 350 uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8), 351 static_cast<uint8_t>(section.header().id)}; 352 sprintf(tmpB, "%c%c", id[0], id[1]); 353 std::string sectionID(tmpB); 354 std::string sectionName = pv::sectionTitles.count(sectionID) 355 ? pv::sectionTitles.at(sectionID) 356 : "Unknown Section"; 357 358 // Add a count if there are multiple of this type of section 359 auto count = pluralSections.find(section.header().id); 360 if (count != pluralSections.end()) 361 { 362 sectionName += " " + std::to_string(count->second); 363 count->second++; 364 } 365 366 if (section.valid()) 367 { 368 std::optional<std::string> json; 369 if (sectionID == "PS" || sectionID == "SS") 370 { 371 json = section.getJSON(registry, plugins, creatorID); 372 } 373 else if ((sectionID == "UD") || (sectionID == "ED")) 374 { 375 json = section.getJSON(creatorID, plugins); 376 } 377 else 378 { 379 json = section.getJSON(creatorID); 380 } 381 382 buf += "\"" + sectionName + "\": {\n"; 383 384 if (json) 385 { 386 buf += *json + "\n},\n"; 387 } 388 else 389 { 390 jsonInsert(buf, pv::sectionVer, 391 getNumberString("%d", section.header().version), 1); 392 jsonInsert(buf, pv::subSection, 393 getNumberString("%d", section.header().subType), 1); 394 jsonInsert(buf, pv::createdBy, 395 getNumberString("0x%X", section.header().componentID), 396 1); 397 398 std::vector<uint8_t> data; 399 Stream s{data}; 400 section.flatten(s); 401 std::string dstr = 402 dumpHex(std::data(data) + SectionHeader::flattenedSize(), 403 data.size() - SectionHeader::flattenedSize(), 2) 404 .get(); 405 std::string jsonIndent(indentLevel, 0x20); 406 buf += jsonIndent + "\"Data\": [\n"; 407 buf += dstr; 408 buf += jsonIndent + "]\n"; 409 buf += "},\n"; 410 } 411 } 412 else 413 { 414 buf += "\n\"Invalid Section\": [\n \"invalid\"\n],\n"; 415 } 416 } 417 418 std::map<uint16_t, size_t> PEL::getPluralSections() const 419 { 420 std::map<uint16_t, size_t> sectionCounts; 421 422 for (const auto& section : optionalSections()) 423 { 424 if (sectionCounts.find(section->header().id) == sectionCounts.end()) 425 { 426 sectionCounts[section->header().id] = 1; 427 } 428 else 429 { 430 sectionCounts[section->header().id]++; 431 } 432 } 433 434 std::map<uint16_t, size_t> sections; 435 for (const auto& [id, count] : sectionCounts) 436 { 437 if (count > 1) 438 { 439 // Start with 0 here and printSectionInJSON() 440 // will increment it as it goes. 441 sections.emplace(id, 0); 442 } 443 } 444 445 return sections; 446 } 447 448 void PEL::toJSON(message::Registry& registry, 449 const std::vector<std::string>& plugins) const 450 { 451 auto sections = getPluralSections(); 452 453 std::string buf = "{\n"; 454 printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins, 455 _ph->creatorID()); 456 printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins, 457 _ph->creatorID()); 458 for (auto& section : this->optionalSections()) 459 { 460 printSectionInJSON(*(section.get()), buf, sections, registry, plugins, 461 _ph->creatorID()); 462 } 463 buf += "}"; 464 std::size_t found = buf.rfind(","); 465 if (found != std::string::npos) 466 buf.replace(found, 1, ""); 467 std::cout << buf << std::endl; 468 } 469 470 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData) 471 { 472 if (size() + userData->header().size > _maxPELSize) 473 { 474 if (userData->shrink(_maxPELSize - size())) 475 { 476 _optionalSections.push_back(std::move(userData)); 477 } 478 else 479 { 480 lg2::warning("Could not shrink UserData section. Dropping. " 481 "Section size = {SSIZE}, Component ID = {COMP_ID}, " 482 "Subtype = {SUBTYPE}, Version = {VERSION}", 483 "SSIZE", userData->header().size, "COMP_ID", 484 userData->header().componentID, "SUBTYPE", 485 userData->header().subType, "VERSION", 486 userData->header().version); 487 return false; 488 } 489 } 490 else 491 { 492 _optionalSections.push_back(std::move(userData)); 493 } 494 return true; 495 } 496 497 nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles) 498 { 499 nlohmann::json callouts; 500 501 for (const auto& file : ffdcFiles) 502 { 503 if ((file.format == UserDataFormat::json) && 504 (file.subType == jsonCalloutSubtype)) 505 { 506 auto data = util::readFD(file.fd); 507 if (data.empty()) 508 { 509 throw std::runtime_error{ 510 "Could not get data from JSON callout file descriptor"}; 511 } 512 513 std::string jsonString{data.begin(), data.begin() + data.size()}; 514 515 callouts = nlohmann::json::parse(jsonString); 516 break; 517 } 518 } 519 520 return callouts; 521 } 522 523 bool PEL::isHwCalloutPresent() const 524 { 525 auto pSRC = primarySRC(); 526 if (!pSRC) 527 { 528 return false; 529 } 530 531 bool calloutPresent = false; 532 if ((*pSRC)->callouts()) 533 { 534 for (auto& i : (*pSRC)->callouts()->callouts()) 535 { 536 if (((*i).fruIdentity())) 537 { 538 auto& fruId = (*i).fruIdentity(); 539 if ((*fruId).failingComponentType() == 540 src::FRUIdentity::hardwareFRU) 541 { 542 calloutPresent = true; 543 break; 544 } 545 } 546 } 547 } 548 549 return calloutPresent; 550 } 551 552 void PEL::updateSysInfoInExtendedUserDataSection( 553 const DataInterfaceBase& dataIface) 554 { 555 const AdditionalData additionalData; 556 557 // Check for PEL from Hostboot 558 if (_ph->creatorID() == static_cast<uint8_t>(CreatorID::hostboot)) 559 { 560 // Get the ED section from PEL 561 auto op = std::find_if( 562 _optionalSections.begin(), _optionalSections.end(), 563 [](auto& section) { 564 return section->header().id == 565 static_cast<uint16_t>(SectionID::extUserData); 566 }); 567 568 // Check for ED section found and its not the last section of PEL 569 if (op != _optionalSections.end()) 570 { 571 // Get the extended user data class mapped to found section 572 auto extUserData = static_cast<ExtendedUserData*>(op->get()); 573 574 // Check for the creator ID is for OpenBMC 575 if (extUserData->creatorID() == 576 static_cast<uint8_t>(CreatorID::openBMC)) 577 { 578 // Update subtype and component id 579 auto subType = static_cast<uint8_t>(UserDataFormat::json); 580 auto componentId = 581 static_cast<uint16_t>(ComponentID::phosphorLogging); 582 583 // Update system data to ED section 584 auto ud = util::makeSysInfoUserDataSection(additionalData, 585 dataIface, false); 586 extUserData->updateDataSection(subType, componentId, 587 ud->data()); 588 } 589 } 590 } 591 } 592 593 bool PEL::getDeconfigFlag() const 594 { 595 auto creator = static_cast<CreatorID>(_ph->creatorID()); 596 597 if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot)) 598 { 599 auto src = primarySRC(); 600 return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured); 601 } 602 return false; 603 } 604 605 bool PEL::getGuardFlag() const 606 { 607 auto creator = static_cast<CreatorID>(_ph->creatorID()); 608 609 if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot)) 610 { 611 auto src = primarySRC(); 612 return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::guarded); 613 } 614 return false; 615 } 616 617 void PEL::updateTerminateBitInSRCSection() 618 { 619 // Check for pel severity of type - 0x51 = critical error, system 620 // termination 621 if (_uh->severity() == 0x51) 622 { 623 // Get the primary SRC section 624 auto pSRC = primarySRC(); 625 if (pSRC) 626 { 627 (*pSRC)->setTerminateBit(); 628 } 629 } 630 } 631 632 void PEL::addJournalSections(const message::Entry& regEntry, 633 const JournalBase& journal) 634 { 635 if (!regEntry.journalCapture) 636 { 637 return; 638 } 639 640 // Write all unwritten journal data to disk. 641 journal.sync(); 642 643 const auto& jc = regEntry.journalCapture.value(); 644 std::vector<std::vector<std::string>> allMessages; 645 646 if (std::holds_alternative<size_t>(jc)) 647 { 648 // Get the previous numLines journal entries 649 const auto& numLines = std::get<size_t>(jc); 650 try 651 { 652 auto messages = journal.getMessages("", numLines); 653 if (!messages.empty()) 654 { 655 allMessages.push_back(std::move(messages)); 656 } 657 } 658 catch (const std::exception& e) 659 { 660 lg2::error("Failed during journal collection: {ERROR}", "ERROR", e); 661 } 662 } 663 else if (std::holds_alternative<message::AppCaptureList>(jc)) 664 { 665 // Get journal entries based on the syslog id field. 666 const auto& sections = std::get<message::AppCaptureList>(jc); 667 for (const auto& [syslogID, numLines] : sections) 668 { 669 try 670 { 671 auto messages = journal.getMessages(syslogID, numLines); 672 if (!messages.empty()) 673 { 674 allMessages.push_back(std::move(messages)); 675 } 676 } 677 catch (const std::exception& e) 678 { 679 lg2::error("Failed during journal collection: {ERROR}", "ERROR", 680 e); 681 } 682 } 683 } 684 685 // Create the UserData sections 686 for (const auto& messages : allMessages) 687 { 688 auto buffer = util::flattenLines(messages); 689 690 // If the buffer is way too big, it can overflow the uint16_t 691 // PEL section size field that is checked below so do a cursory 692 // check here. 693 if (buffer.size() > _maxPELSize) 694 { 695 lg2::warning( 696 "Journal UserData section does not fit in PEL, dropping. " 697 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}", 698 "PEL_SIZE", size(), "DATA_SIZE", buffer.size()); 699 continue; 700 } 701 702 // Sections must be 4 byte aligned. 703 while (buffer.size() % 4 != 0) 704 { 705 buffer.push_back(0); 706 } 707 708 auto ud = std::make_unique<UserData>( 709 static_cast<uint16_t>(ComponentID::phosphorLogging), 710 static_cast<uint8_t>(UserDataFormat::text), 711 static_cast<uint8_t>(UserDataFormatVersion::text), buffer); 712 713 if (size() + ud->header().size <= _maxPELSize) 714 { 715 _optionalSections.push_back(std::move(ud)); 716 } 717 else 718 { 719 // Don't attempt to shrink here since we'd be dropping the 720 // most recent journal entries which would be confusing. 721 lg2::warning( 722 "Journal UserData section does not fit in PEL, dropping. " 723 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}", 724 "PEL_SIZE", size(), "DATA_SIZE", buffer.size()); 725 ud.reset(); 726 continue; 727 } 728 } 729 } 730 731 void PEL::addAdDetailsForDIMMsCallout( 732 const std::unique_ptr<SRC>& src, const DataInterfaceBase& dataIface, 733 nlohmann::json& adSysInfoData, DebugData& debugData) 734 { 735 if (!src->callouts()) 736 { 737 // No callouts 738 return; 739 } 740 741 auto isDIMMCallout = [&dataIface, &debugData](const auto& callout) { 742 auto locCode{callout->locationCode()}; 743 if (locCode.empty()) 744 { 745 // Not a hardware callout. No action required 746 return false; 747 } 748 else 749 { 750 return const_cast<DataInterfaceBase&>(dataIface).isDIMM(locCode); 751 } 752 }; 753 auto addAdDIMMDetails = [&dataIface, &adSysInfoData, 754 &debugData](const auto& callout) { 755 auto dimmLocCode{callout->locationCode()}; 756 757 auto diPropVal = dataIface.getDIProperty(dimmLocCode); 758 if (!diPropVal.has_value()) 759 { 760 std::string errMsg{ 761 std::format("Failed reading DI property from " 762 "VINI Interface for the LocationCode:[{}]", 763 dimmLocCode)}; 764 debugData[AdDIMMInfoFetchError].emplace_back(errMsg); 765 } 766 else 767 { 768 util::addDIMMInfo(dimmLocCode, diPropVal.value(), adSysInfoData); 769 } 770 }; 771 772 auto DIMMsCallouts = src->callouts()->callouts() | 773 std::views::filter(isDIMMCallout); 774 775 std::ranges::for_each(DIMMsCallouts, addAdDIMMDetails); 776 } 777 778 namespace util 779 { 780 781 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json) 782 { 783 auto jsonString = json.dump(); 784 std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end()); 785 786 // Pad to a 4 byte boundary 787 while ((jsonData.size() % 4) != 0) 788 { 789 jsonData.push_back(0); 790 } 791 792 return std::make_unique<UserData>( 793 static_cast<uint16_t>(ComponentID::phosphorLogging), 794 static_cast<uint8_t>(UserDataFormat::json), 795 static_cast<uint8_t>(UserDataFormatVersion::json), jsonData); 796 } 797 798 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad) 799 { 800 assert(!ad.empty()); 801 nlohmann::json json; 802 803 // Remove the 'ESEL' entry, as it contains a full PEL in the value. 804 if (ad.getValue("ESEL")) 805 { 806 auto newAD = ad; 807 newAD.remove("ESEL"); 808 json = newAD.toJSON(); 809 } 810 else 811 { 812 json = ad.toJSON(); 813 } 814 815 return makeJSONUserDataSection(json); 816 } 817 818 void addProcessNameToJSON(nlohmann::json& json, 819 const std::optional<std::string>& pid, 820 const DataInterfaceBase& dataIface) 821 { 822 std::string name{unknownValue}; 823 824 try 825 { 826 if (pid) 827 { 828 auto n = dataIface.getProcessName(*pid); 829 if (n) 830 { 831 name = *n; 832 } 833 } 834 } 835 catch (const std::exception& e) 836 {} 837 838 if (pid) 839 { 840 json["Process Name"] = std::move(name); 841 } 842 } 843 844 void addBMCFWVersionIDToJSON(nlohmann::json& json, 845 const DataInterfaceBase& dataIface) 846 { 847 auto id = dataIface.getBMCFWVersionID(); 848 if (id.empty()) 849 { 850 id = unknownValue; 851 } 852 853 json["FW Version ID"] = std::move(id); 854 } 855 856 std::string lastSegment(char separator, std::string data) 857 { 858 auto pos = data.find_last_of(separator); 859 if (pos != std::string::npos) 860 { 861 data = data.substr(pos + 1); 862 } 863 864 return data; 865 } 866 867 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface) 868 { 869 auto keyword = dataIface.getSystemIMKeyword(); 870 871 std::string value{}; 872 873 std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) { 874 value += std::format("{:02X}", byte); 875 }); 876 877 json["System IM"] = value; 878 } 879 880 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface) 881 { 882 json["BMCState"] = lastSegment('.', dataIface.getBMCState()); 883 json["ChassisState"] = lastSegment('.', dataIface.getChassisState()); 884 json["HostState"] = lastSegment('.', dataIface.getHostState()); 885 json["BootState"] = lastSegment('.', dataIface.getBootState()); 886 } 887 888 void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface) 889 { 890 auto seconds = dataIface.getUptimeInSeconds(); 891 if (seconds) 892 { 893 json["BMCUptime"] = dataIface.getBMCUptime(*seconds); 894 } 895 else 896 { 897 json["BMCUptime"] = ""; 898 } 899 json["BMCLoad"] = dataIface.getBMCLoadAvg(); 900 } 901 902 std::unique_ptr<UserData> makeSysInfoUserDataSection( 903 const AdditionalData& ad, const DataInterfaceBase& dataIface, 904 bool addUptime, const nlohmann::json& adSysInfoData) 905 { 906 nlohmann::json json; 907 908 addProcessNameToJSON(json, ad.getValue("_PID"), dataIface); 909 addBMCFWVersionIDToJSON(json, dataIface); 910 addIMKeyword(json, dataIface); 911 addStatesToJSON(json, dataIface); 912 913 if (addUptime) 914 { 915 addBMCUptime(json, dataIface); 916 } 917 918 if (!adSysInfoData.empty()) 919 { 920 json.update(adSysInfoData); 921 } 922 923 return makeJSONUserDataSection(json); 924 } 925 926 std::vector<uint8_t> readFD(int fd) 927 { 928 std::vector<uint8_t> data; 929 930 // Get the size 931 struct stat s; 932 int r = fstat(fd, &s); 933 if (r != 0) 934 { 935 auto e = errno; 936 lg2::error("Could not get FFDC file size from FD, errno = {ERRNO}", 937 "ERRNO", e); 938 return data; 939 } 940 941 if (0 == s.st_size) 942 { 943 lg2::error("FFDC file is empty"); 944 return data; 945 } 946 947 data.resize(s.st_size); 948 949 // Make sure its at the beginning, as maybe another 950 // extension already used it. 951 r = lseek(fd, 0, SEEK_SET); 952 if (r == -1) 953 { 954 auto e = errno; 955 lg2::error("Could not seek to beginning of FFDC file, errno = {ERRNO}", 956 "ERRNO", e); 957 return data; 958 } 959 960 r = read(fd, data.data(), s.st_size); 961 if (r == -1) 962 { 963 auto e = errno; 964 lg2::error("Could not read FFDC file, errno = {ERRNO}", "ERRNO", e); 965 } 966 else if (r != s.st_size) 967 { 968 lg2::warning("Could not read full FFDC file. " 969 "File size = {FSIZE}, Size read = {SIZE_READ}", 970 "FSIZE", s.st_size, "SIZE_READ", r); 971 } 972 973 return data; 974 } 975 976 std::unique_ptr<UserData> 977 makeFFDCuserDataSection(uint16_t componentID, const PelFFDCfile& file) 978 { 979 auto data = readFD(file.fd); 980 981 if (data.empty()) 982 { 983 return std::unique_ptr<UserData>(); 984 } 985 986 // The data needs 4 Byte alignment, and save amount padded for the 987 // CBOR case. 988 uint32_t pad = 0; 989 while (data.size() % 4) 990 { 991 data.push_back(0); 992 pad++; 993 } 994 995 // For JSON, CBOR, and Text use our component ID, subType, and version, 996 // otherwise use the supplied ones. 997 uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging); 998 uint8_t subType{}; 999 uint8_t version{}; 1000 1001 switch (file.format) 1002 { 1003 case UserDataFormat::json: 1004 subType = static_cast<uint8_t>(UserDataFormat::json); 1005 version = static_cast<uint8_t>(UserDataFormatVersion::json); 1006 break; 1007 case UserDataFormat::cbor: 1008 subType = static_cast<uint8_t>(UserDataFormat::cbor); 1009 version = static_cast<uint8_t>(UserDataFormatVersion::cbor); 1010 1011 // The CBOR parser will fail on the extra pad bytes since they 1012 // aren't CBOR. Add the amount we padded to the end and other 1013 // code will remove it all before parsing. 1014 { 1015 data.resize(data.size() + 4); 1016 Stream stream{data}; 1017 stream.offset(data.size() - 4); 1018 stream << pad; 1019 } 1020 1021 break; 1022 case UserDataFormat::text: 1023 subType = static_cast<uint8_t>(UserDataFormat::text); 1024 version = static_cast<uint8_t>(UserDataFormatVersion::text); 1025 break; 1026 case UserDataFormat::custom: 1027 default: 1028 // Use the passed in values 1029 compID = componentID; 1030 subType = file.subType; 1031 version = file.version; 1032 break; 1033 } 1034 1035 return std::make_unique<UserData>(compID, subType, version, data); 1036 } 1037 1038 std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines) 1039 { 1040 std::vector<uint8_t> out; 1041 1042 for (const auto& line : lines) 1043 { 1044 out.insert(out.end(), line.begin(), line.end()); 1045 1046 if (out.back() != '\n') 1047 { 1048 out.push_back('\n'); 1049 } 1050 } 1051 1052 return out; 1053 } 1054 1055 void addDIMMInfo(const std::string& locationCode, 1056 const std::vector<std::uint8_t>& diPropVal, 1057 nlohmann::json& adSysInfoData) 1058 { 1059 nlohmann::json dimmInfoObj; 1060 dimmInfoObj["Location Code"] = locationCode; 1061 std::ranges::transform( 1062 diPropVal, std::back_inserter(dimmInfoObj["DRAM Manufacturer ID"]), 1063 [](const auto& diPropEachByte) { 1064 return std::format("{:#04x}", diPropEachByte); 1065 }); 1066 adSysInfoData["DIMMs Additional Info"] += dimmInfoObj; 1067 } 1068 1069 } // namespace util 1070 1071 } // namespace pels 1072 } // namespace openpower 1073