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