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 auto isDIMMLocCode = 751 const_cast<DataInterfaceBase&>(dataIface).isDIMM(locCode); 752 if (isDIMMLocCode.has_value()) 753 { 754 return isDIMMLocCode.value(); 755 } 756 debugData[AdDIMMInfoFetchError].emplace_back(isDIMMLocCode.error()); 757 return false; 758 } 759 }; 760 auto addAdDIMMDetails = [&dataIface, &adSysInfoData, 761 &debugData](const auto& callout) { 762 auto dimmLocCode{callout->locationCode()}; 763 764 auto diPropVal = dataIface.getDIProperty(dimmLocCode); 765 if (!diPropVal.has_value()) 766 { 767 std::string errMsg{ 768 std::format("Failed reading DI property from " 769 "VINI Interface for the LocationCode:[{}]", 770 dimmLocCode)}; 771 debugData[AdDIMMInfoFetchError].emplace_back(errMsg); 772 } 773 else 774 { 775 util::addDIMMInfo(dimmLocCode, diPropVal.value(), adSysInfoData); 776 } 777 }; 778 779 auto DIMMsCallouts = src->callouts()->callouts() | 780 std::views::filter(isDIMMCallout); 781 782 std::ranges::for_each(DIMMsCallouts, addAdDIMMDetails); 783 } 784 785 namespace util 786 { 787 788 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json) 789 { 790 auto jsonString = json.dump(); 791 std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end()); 792 793 // Pad to a 4 byte boundary 794 while ((jsonData.size() % 4) != 0) 795 { 796 jsonData.push_back(0); 797 } 798 799 return std::make_unique<UserData>( 800 static_cast<uint16_t>(ComponentID::phosphorLogging), 801 static_cast<uint8_t>(UserDataFormat::json), 802 static_cast<uint8_t>(UserDataFormatVersion::json), jsonData); 803 } 804 805 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad) 806 { 807 assert(!ad.empty()); 808 nlohmann::json json; 809 810 // Remove the 'ESEL' entry, as it contains a full PEL in the value. 811 if (ad.getValue("ESEL")) 812 { 813 auto newAD = ad; 814 newAD.remove("ESEL"); 815 json = newAD.toJSON(); 816 } 817 else 818 { 819 json = ad.toJSON(); 820 } 821 822 return makeJSONUserDataSection(json); 823 } 824 825 void addProcessNameToJSON(nlohmann::json& json, 826 const std::optional<std::string>& pid, 827 const DataInterfaceBase& dataIface) 828 { 829 std::string name{unknownValue}; 830 831 try 832 { 833 if (pid) 834 { 835 auto n = dataIface.getProcessName(*pid); 836 if (n) 837 { 838 name = *n; 839 } 840 } 841 } 842 catch (const std::exception& e) 843 {} 844 845 if (pid) 846 { 847 json["Process Name"] = std::move(name); 848 } 849 } 850 851 void addBMCFWVersionIDToJSON(nlohmann::json& json, 852 const DataInterfaceBase& dataIface) 853 { 854 auto id = dataIface.getBMCFWVersionID(); 855 if (id.empty()) 856 { 857 id = unknownValue; 858 } 859 860 json["FW Version ID"] = std::move(id); 861 } 862 863 std::string lastSegment(char separator, std::string data) 864 { 865 auto pos = data.find_last_of(separator); 866 if (pos != std::string::npos) 867 { 868 data = data.substr(pos + 1); 869 } 870 871 return data; 872 } 873 874 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface) 875 { 876 auto keyword = dataIface.getSystemIMKeyword(); 877 878 std::string value{}; 879 880 std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) { 881 value += std::format("{:02X}", byte); 882 }); 883 884 json["System IM"] = value; 885 } 886 887 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface) 888 { 889 json["BMCState"] = lastSegment('.', dataIface.getBMCState()); 890 json["ChassisState"] = lastSegment('.', dataIface.getChassisState()); 891 json["HostState"] = lastSegment('.', dataIface.getHostState()); 892 json["BootState"] = lastSegment('.', dataIface.getBootState()); 893 } 894 895 void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface) 896 { 897 auto seconds = dataIface.getUptimeInSeconds(); 898 if (seconds) 899 { 900 json["BMCUptime"] = dataIface.getBMCUptime(*seconds); 901 } 902 else 903 { 904 json["BMCUptime"] = ""; 905 } 906 json["BMCLoad"] = dataIface.getBMCLoadAvg(); 907 } 908 909 std::unique_ptr<UserData> makeSysInfoUserDataSection( 910 const AdditionalData& ad, const DataInterfaceBase& dataIface, 911 bool addUptime, const nlohmann::json& adSysInfoData) 912 { 913 nlohmann::json json; 914 915 addProcessNameToJSON(json, ad.getValue("_PID"), dataIface); 916 addBMCFWVersionIDToJSON(json, dataIface); 917 addIMKeyword(json, dataIface); 918 addStatesToJSON(json, dataIface); 919 920 if (addUptime) 921 { 922 addBMCUptime(json, dataIface); 923 } 924 925 if (!adSysInfoData.empty()) 926 { 927 json.update(adSysInfoData); 928 } 929 930 return makeJSONUserDataSection(json); 931 } 932 933 std::vector<uint8_t> readFD(int fd) 934 { 935 std::vector<uint8_t> data; 936 937 // Get the size 938 struct stat s; 939 int r = fstat(fd, &s); 940 if (r != 0) 941 { 942 auto e = errno; 943 lg2::error("Could not get FFDC file size from FD, errno = {ERRNO}", 944 "ERRNO", e); 945 return data; 946 } 947 948 if (0 == s.st_size) 949 { 950 lg2::error("FFDC file is empty"); 951 return data; 952 } 953 954 data.resize(s.st_size); 955 956 // Make sure its at the beginning, as maybe another 957 // extension already used it. 958 r = lseek(fd, 0, SEEK_SET); 959 if (r == -1) 960 { 961 auto e = errno; 962 lg2::error("Could not seek to beginning of FFDC file, errno = {ERRNO}", 963 "ERRNO", e); 964 return data; 965 } 966 967 r = read(fd, data.data(), s.st_size); 968 if (r == -1) 969 { 970 auto e = errno; 971 lg2::error("Could not read FFDC file, errno = {ERRNO}", "ERRNO", e); 972 } 973 else if (r != s.st_size) 974 { 975 lg2::warning("Could not read full FFDC file. " 976 "File size = {FSIZE}, Size read = {SIZE_READ}", 977 "FSIZE", s.st_size, "SIZE_READ", r); 978 } 979 980 return data; 981 } 982 983 std::unique_ptr<UserData> 984 makeFFDCuserDataSection(uint16_t componentID, const PelFFDCfile& file) 985 { 986 auto data = readFD(file.fd); 987 988 if (data.empty()) 989 { 990 return std::unique_ptr<UserData>(); 991 } 992 993 // The data needs 4 Byte alignment, and save amount padded for the 994 // CBOR case. 995 uint32_t pad = 0; 996 while (data.size() % 4) 997 { 998 data.push_back(0); 999 pad++; 1000 } 1001 1002 // For JSON, CBOR, and Text use our component ID, subType, and version, 1003 // otherwise use the supplied ones. 1004 uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging); 1005 uint8_t subType{}; 1006 uint8_t version{}; 1007 1008 switch (file.format) 1009 { 1010 case UserDataFormat::json: 1011 subType = static_cast<uint8_t>(UserDataFormat::json); 1012 version = static_cast<uint8_t>(UserDataFormatVersion::json); 1013 break; 1014 case UserDataFormat::cbor: 1015 subType = static_cast<uint8_t>(UserDataFormat::cbor); 1016 version = static_cast<uint8_t>(UserDataFormatVersion::cbor); 1017 1018 // The CBOR parser will fail on the extra pad bytes since they 1019 // aren't CBOR. Add the amount we padded to the end and other 1020 // code will remove it all before parsing. 1021 { 1022 data.resize(data.size() + 4); 1023 Stream stream{data}; 1024 stream.offset(data.size() - 4); 1025 stream << pad; 1026 } 1027 1028 break; 1029 case UserDataFormat::text: 1030 subType = static_cast<uint8_t>(UserDataFormat::text); 1031 version = static_cast<uint8_t>(UserDataFormatVersion::text); 1032 break; 1033 case UserDataFormat::custom: 1034 default: 1035 // Use the passed in values 1036 compID = componentID; 1037 subType = file.subType; 1038 version = file.version; 1039 break; 1040 } 1041 1042 return std::make_unique<UserData>(compID, subType, version, data); 1043 } 1044 1045 std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines) 1046 { 1047 std::vector<uint8_t> out; 1048 1049 for (const auto& line : lines) 1050 { 1051 out.insert(out.end(), line.begin(), line.end()); 1052 1053 if (out.back() != '\n') 1054 { 1055 out.push_back('\n'); 1056 } 1057 } 1058 1059 return out; 1060 } 1061 1062 void addDIMMInfo(const std::string& locationCode, 1063 const std::vector<std::uint8_t>& diPropVal, 1064 nlohmann::json& adSysInfoData) 1065 { 1066 nlohmann::json dimmInfoObj; 1067 dimmInfoObj["Location Code"] = locationCode; 1068 std::ranges::transform( 1069 diPropVal, std::back_inserter(dimmInfoObj["DRAM Manufacturer ID"]), 1070 [](const auto& diPropEachByte) { 1071 return std::format("{:#04x}", diPropEachByte); 1072 }); 1073 adSysInfoData["DIMMs Additional Info"] += dimmInfoObj; 1074 } 1075 1076 } // namespace util 1077 1078 } // namespace pels 1079 } // namespace openpower 1080