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 "json_utils.hpp" 25 #include "log_id.hpp" 26 #include "pel_rules.hpp" 27 #include "pel_values.hpp" 28 #include "section_factory.hpp" 29 #include "src.hpp" 30 #include "stream.hpp" 31 #include "user_data_formats.hpp" 32 33 #ifdef PEL_ENABLE_PHAL 34 #include "phal_service_actions.hpp" 35 #include "sbe_ffdc_handler.hpp" 36 #endif 37 38 #include <fmt/format.h> 39 #include <sys/stat.h> 40 #include <unistd.h> 41 42 #include <iostream> 43 #include <phosphor-logging/log.hpp> 44 45 namespace openpower 46 { 47 namespace pels 48 { 49 namespace message = openpower::pels::message; 50 namespace pv = openpower::pels::pel_values; 51 using namespace phosphor::logging; 52 53 constexpr auto unknownValue = "Unknown"; 54 55 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp, 56 phosphor::logging::Entry::Level severity, 57 const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn, 58 const DataInterfaceBase& dataIface) 59 { 60 // No changes in input, for non SBE error related requests 61 PelFFDC ffdcFiles = ffdcFilesIn; 62 63 #ifdef PEL_ENABLE_PHAL 64 // Add sbe ffdc processed data into ffdcfiles. 65 namespace sbe = openpower::pels::sbe; 66 auto processReq = 67 std::any_of(ffdcFiles.begin(), ffdcFiles.end(), [](const auto& file) { 68 return file.format == UserDataFormat::custom && 69 file.subType == sbe::sbeFFDCSubType; 70 }); 71 // sbeFFDC can't be destroyed until the end of the PEL constructor 72 // because it needs to keep around the FFDC Files to be used below. 73 std::unique_ptr<sbe::SbeFFDC> sbeFFDCPtr; 74 if (processReq) 75 { 76 sbeFFDCPtr = 77 std::make_unique<sbe::SbeFFDC>(additionalData, ffdcFilesIn); 78 const auto& sbeFFDCFiles = sbeFFDCPtr->getSbeFFDC(); 79 ffdcFiles.insert(ffdcFiles.end(), sbeFFDCFiles.begin(), 80 sbeFFDCFiles.end()); 81 } 82 #endif 83 84 std::map<std::string, std::vector<std::string>> debugData; 85 nlohmann::json callouts; 86 87 _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID, 88 timestamp); 89 _uh = std::make_unique<UserHeader>(regEntry, severity, additionalData, 90 dataIface); 91 92 // Extract any callouts embedded in an FFDC file. 93 if (!ffdcFiles.empty()) 94 { 95 try 96 { 97 callouts = getCalloutJSON(ffdcFiles); 98 } 99 catch (const std::exception& e) 100 { 101 debugData.emplace("FFDC file JSON callouts error", 102 std::vector<std::string>{e.what()}); 103 } 104 } 105 106 auto src = 107 std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface); 108 109 if (!src->getDebugData().empty()) 110 { 111 // Something didn't go as planned 112 debugData.emplace("SRC", src->getDebugData()); 113 } 114 115 auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src); 116 117 _optionalSections.push_back(std::move(src)); 118 _optionalSections.push_back(std::move(euh)); 119 120 auto mtms = std::make_unique<FailingMTMS>(dataIface); 121 _optionalSections.push_back(std::move(mtms)); 122 123 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); 124 addUserDataSection(std::move(ud)); 125 126 // Create a UserData section from AdditionalData. 127 if (!additionalData.empty()) 128 { 129 ud = util::makeADUserDataSection(additionalData); 130 addUserDataSection(std::move(ud)); 131 } 132 133 // Add any FFDC files into UserData sections 134 for (const auto& file : ffdcFiles) 135 { 136 ud = util::makeFFDCuserDataSection(regEntry.componentID, file); 137 if (!ud) 138 { 139 // Add this error into the debug data UserData section 140 std::ostringstream msg; 141 msg << "Could not make PEL FFDC UserData section from file" 142 << std::hex << regEntry.componentID << " " << file.subType 143 << " " << file.version; 144 if (debugData.count("FFDC File")) 145 { 146 debugData.at("FFDC File").push_back(msg.str()); 147 } 148 else 149 { 150 debugData.emplace("FFDC File", 151 std::vector<std::string>{msg.str()}); 152 } 153 154 continue; 155 } 156 157 addUserDataSection(std::move(ud)); 158 } 159 160 #ifdef PEL_ENABLE_PHAL 161 auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID); 162 openpower::pels::phal::createServiceActions(callouts, path, dataIface, 163 plid()); 164 #endif 165 166 // Store in the PEL any important debug data created while 167 // building the PEL sections. 168 if (!debugData.empty()) 169 { 170 nlohmann::json data; 171 data["PEL Internal Debug Data"] = debugData; 172 ud = util::makeJSONUserDataSection(data); 173 174 addUserDataSection(std::move(ud)); 175 176 // Also put in the journal for debug 177 for (const auto& [name, data] : debugData) 178 { 179 for (const auto& message : data) 180 { 181 std::string entry = name + ": " + message; 182 log<level::INFO>(entry.c_str()); 183 } 184 } 185 } 186 187 _ph->setSectionCount(2 + _optionalSections.size()); 188 189 checkRulesAndFix(); 190 } 191 192 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0) 193 { 194 } 195 196 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID) 197 { 198 populateFromRawData(data, obmcLogID); 199 } 200 201 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID) 202 { 203 Stream pelData{data}; 204 _ph = std::make_unique<PrivateHeader>(pelData); 205 if (obmcLogID != 0) 206 { 207 _ph->setOBMCLogID(obmcLogID); 208 } 209 210 _uh = std::make_unique<UserHeader>(pelData); 211 212 // Use the section factory to create the rest of the objects 213 for (size_t i = 2; i < _ph->sectionCount(); i++) 214 { 215 auto section = section_factory::create(pelData); 216 _optionalSections.push_back(std::move(section)); 217 } 218 } 219 220 bool PEL::valid() const 221 { 222 bool valid = _ph->valid(); 223 224 if (valid) 225 { 226 valid = _uh->valid(); 227 } 228 229 if (valid) 230 { 231 if (!std::all_of(_optionalSections.begin(), _optionalSections.end(), 232 [](const auto& section) { return section->valid(); })) 233 { 234 valid = false; 235 } 236 } 237 238 return valid; 239 } 240 241 void PEL::setCommitTime() 242 { 243 auto now = std::chrono::system_clock::now(); 244 _ph->setCommitTimestamp(getBCDTime(now)); 245 } 246 247 void PEL::assignID() 248 { 249 _ph->setID(generatePELID()); 250 } 251 252 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const 253 { 254 Stream pelData{pelBuffer}; 255 256 if (!valid()) 257 { 258 log<level::WARNING>("Unflattening an invalid PEL"); 259 } 260 261 _ph->flatten(pelData); 262 _uh->flatten(pelData); 263 264 for (auto& section : _optionalSections) 265 { 266 section->flatten(pelData); 267 } 268 } 269 270 std::vector<uint8_t> PEL::data() const 271 { 272 std::vector<uint8_t> pelData; 273 flatten(pelData); 274 return pelData; 275 } 276 277 size_t PEL::size() const 278 { 279 size_t size = 0; 280 281 if (_ph) 282 { 283 size += _ph->header().size; 284 } 285 286 if (_uh) 287 { 288 size += _uh->header().size; 289 } 290 291 for (const auto& section : _optionalSections) 292 { 293 size += section->header().size; 294 } 295 296 return size; 297 } 298 299 std::optional<SRC*> PEL::primarySRC() const 300 { 301 auto src = std::find_if( 302 _optionalSections.begin(), _optionalSections.end(), [](auto& section) { 303 return section->header().id == 304 static_cast<uint16_t>(SectionID::primarySRC); 305 }); 306 if (src != _optionalSections.end()) 307 { 308 return static_cast<SRC*>(src->get()); 309 } 310 311 return std::nullopt; 312 } 313 314 void PEL::checkRulesAndFix() 315 { 316 // Only fix if the action flags are at their default value which 317 // means they weren't specified in the registry. Otherwise 318 // assume the user knows what they are doing. 319 if (_uh->actionFlags() == actionFlagsDefault) 320 { 321 auto [actionFlags, eventType] = 322 pel_rules::check(0, _uh->eventType(), _uh->severity()); 323 324 _uh->setActionFlags(actionFlags); 325 _uh->setEventType(eventType); 326 } 327 } 328 329 void PEL::printSectionInJSON(const Section& section, std::string& buf, 330 std::map<uint16_t, size_t>& pluralSections, 331 message::Registry& registry, 332 const std::vector<std::string>& plugins, 333 uint8_t creatorID) const 334 { 335 char tmpB[5]; 336 uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8), 337 static_cast<uint8_t>(section.header().id)}; 338 sprintf(tmpB, "%c%c", id[0], id[1]); 339 std::string sectionID(tmpB); 340 std::string sectionName = pv::sectionTitles.count(sectionID) 341 ? pv::sectionTitles.at(sectionID) 342 : "Unknown Section"; 343 344 // Add a count if there are multiple of this type of section 345 auto count = pluralSections.find(section.header().id); 346 if (count != pluralSections.end()) 347 { 348 sectionName += " " + std::to_string(count->second); 349 count->second++; 350 } 351 352 if (section.valid()) 353 { 354 std::optional<std::string> json; 355 if (sectionID == "PS" || sectionID == "SS") 356 { 357 json = section.getJSON(registry, plugins, creatorID); 358 } 359 else if ((sectionID == "UD") || (sectionID == "ED")) 360 { 361 json = section.getJSON(creatorID, plugins); 362 } 363 else 364 { 365 json = section.getJSON(); 366 } 367 368 buf += "\"" + sectionName + "\": {\n"; 369 370 if (json) 371 { 372 buf += *json + "\n},\n"; 373 } 374 else 375 { 376 jsonInsert(buf, pv::sectionVer, 377 getNumberString("%d", section.header().version), 1); 378 jsonInsert(buf, pv::subSection, 379 getNumberString("%d", section.header().subType), 1); 380 jsonInsert(buf, pv::createdBy, 381 getNumberString("0x%X", section.header().componentID), 382 1); 383 384 std::vector<uint8_t> data; 385 Stream s{data}; 386 section.flatten(s); 387 std::string dstr = 388 dumpHex(std::data(data) + SectionHeader::flattenedSize(), 389 data.size() - SectionHeader::flattenedSize(), 2); 390 std::string jsonIndent(indentLevel, 0x20); 391 buf += jsonIndent + "\"Data\": [\n"; 392 buf += dstr; 393 buf += jsonIndent + "]\n"; 394 buf += "},\n"; 395 } 396 } 397 else 398 { 399 buf += "\n\"Invalid Section\": [\n \"invalid\"\n],\n"; 400 } 401 } 402 403 std::map<uint16_t, size_t> PEL::getPluralSections() const 404 { 405 std::map<uint16_t, size_t> sectionCounts; 406 407 for (const auto& section : optionalSections()) 408 { 409 if (sectionCounts.find(section->header().id) == sectionCounts.end()) 410 { 411 sectionCounts[section->header().id] = 1; 412 } 413 else 414 { 415 sectionCounts[section->header().id]++; 416 } 417 } 418 419 std::map<uint16_t, size_t> sections; 420 for (const auto& [id, count] : sectionCounts) 421 { 422 if (count > 1) 423 { 424 // Start with 0 here and printSectionInJSON() 425 // will increment it as it goes. 426 sections.emplace(id, 0); 427 } 428 } 429 430 return sections; 431 } 432 433 void PEL::toJSON(message::Registry& registry, 434 const std::vector<std::string>& plugins) const 435 { 436 auto sections = getPluralSections(); 437 438 std::string buf = "{\n"; 439 printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins); 440 printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins); 441 for (auto& section : this->optionalSections()) 442 { 443 printSectionInJSON(*(section.get()), buf, sections, registry, plugins, 444 _ph->creatorID()); 445 } 446 buf += "}"; 447 std::size_t found = buf.rfind(","); 448 if (found != std::string::npos) 449 buf.replace(found, 1, ""); 450 std::cout << buf << std::endl; 451 } 452 453 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData) 454 { 455 if (size() + userData->header().size > _maxPELSize) 456 { 457 if (userData->shrink(_maxPELSize - size())) 458 { 459 _optionalSections.push_back(std::move(userData)); 460 } 461 else 462 { 463 log<level::WARNING>( 464 "Could not shrink UserData section. Dropping", 465 entry("SECTION_SIZE=%d\n", userData->header().size), 466 entry("COMPONENT_ID=0x%02X", userData->header().componentID), 467 entry("SUBTYPE=0x%X", userData->header().subType), 468 entry("VERSION=0x%X", userData->header().version)); 469 return false; 470 } 471 } 472 else 473 { 474 _optionalSections.push_back(std::move(userData)); 475 } 476 return true; 477 } 478 479 nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles) 480 { 481 nlohmann::json callouts; 482 483 for (const auto& file : ffdcFiles) 484 { 485 if ((file.format == UserDataFormat::json) && 486 (file.subType == jsonCalloutSubtype)) 487 { 488 auto data = util::readFD(file.fd); 489 if (data.empty()) 490 { 491 throw std::runtime_error{ 492 "Could not get data from JSON callout file descriptor"}; 493 } 494 495 std::string jsonString{data.begin(), data.begin() + data.size()}; 496 497 callouts = nlohmann::json::parse(jsonString); 498 break; 499 } 500 } 501 502 return callouts; 503 } 504 505 bool PEL::isCalloutPresent() const 506 { 507 auto pSRC = primarySRC(); 508 if (!pSRC) 509 { 510 return false; 511 } 512 513 bool calloutPresent = false; 514 if ((*pSRC)->callouts()) 515 { 516 for (auto& i : (*pSRC)->callouts()->callouts()) 517 { 518 if (((*i).fruIdentity())) 519 { 520 auto& fruId = (*i).fruIdentity(); 521 if ((*fruId).failingComponentType() != 0) 522 { 523 calloutPresent = true; 524 break; 525 } 526 } 527 } 528 } 529 530 return calloutPresent; 531 } 532 533 void PEL::updateSysInfoInExtendedUserDataSection( 534 const DataInterfaceBase& dataIface) 535 { 536 const AdditionalData additionalData; 537 538 // Check for PEL from Hostboot 539 if (_ph->creatorID() == static_cast<uint8_t>(CreatorID::hostboot)) 540 { 541 // Get the ED section from PEL 542 auto op = std::find_if(_optionalSections.begin(), 543 _optionalSections.end(), [](auto& section) { 544 return section->header().id == 545 static_cast<uint16_t>( 546 SectionID::extUserData); 547 }); 548 549 // Check for ED section found and its not the last section of PEL 550 if (op != _optionalSections.end()) 551 { 552 // Get the extended user data class mapped to found section 553 auto extUserData = static_cast<ExtendedUserData*>(op->get()); 554 555 // Check for the creator ID is for OpenBMC 556 if (extUserData->creatorID() == 557 static_cast<uint8_t>(CreatorID::openBMC)) 558 { 559 // Update subtype and component id 560 auto subType = static_cast<uint8_t>(UserDataFormat::json); 561 auto componentId = 562 static_cast<uint16_t>(ComponentID::phosphorLogging); 563 564 // Update system data to ED section 565 auto ud = 566 util::makeSysInfoUserDataSection(additionalData, dataIface); 567 extUserData->updateDataSection(subType, componentId, 568 ud->data()); 569 } 570 } 571 } 572 } 573 574 namespace util 575 { 576 577 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json) 578 { 579 auto jsonString = json.dump(); 580 std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end()); 581 582 // Pad to a 4 byte boundary 583 while ((jsonData.size() % 4) != 0) 584 { 585 jsonData.push_back(0); 586 } 587 588 return std::make_unique<UserData>( 589 static_cast<uint16_t>(ComponentID::phosphorLogging), 590 static_cast<uint8_t>(UserDataFormat::json), 591 static_cast<uint8_t>(UserDataFormatVersion::json), jsonData); 592 } 593 594 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad) 595 { 596 assert(!ad.empty()); 597 nlohmann::json json; 598 599 // Remove the 'ESEL' entry, as it contains a full PEL in the value. 600 if (ad.getValue("ESEL")) 601 { 602 auto newAD = ad; 603 newAD.remove("ESEL"); 604 json = newAD.toJSON(); 605 } 606 else 607 { 608 json = ad.toJSON(); 609 } 610 611 return makeJSONUserDataSection(json); 612 } 613 614 void addProcessNameToJSON(nlohmann::json& json, 615 const std::optional<std::string>& pid, 616 const DataInterfaceBase& dataIface) 617 { 618 std::string name{unknownValue}; 619 620 try 621 { 622 if (pid) 623 { 624 auto n = dataIface.getProcessName(*pid); 625 if (n) 626 { 627 name = *n; 628 } 629 } 630 } 631 catch (const std::exception& e) 632 { 633 } 634 635 if (pid) 636 { 637 json["Process Name"] = std::move(name); 638 } 639 } 640 641 void addBMCFWVersionIDToJSON(nlohmann::json& json, 642 const DataInterfaceBase& dataIface) 643 { 644 auto id = dataIface.getBMCFWVersionID(); 645 if (id.empty()) 646 { 647 id = unknownValue; 648 } 649 650 json["FW Version ID"] = std::move(id); 651 } 652 653 std::string lastSegment(char separator, std::string data) 654 { 655 auto pos = data.find_last_of(separator); 656 if (pos != std::string::npos) 657 { 658 data = data.substr(pos + 1); 659 } 660 661 return data; 662 } 663 664 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface) 665 { 666 auto keyword = dataIface.getSystemIMKeyword(); 667 668 std::string value{}; 669 670 std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) { 671 value += fmt::format("{:02X}", byte); 672 }); 673 674 json["System IM"] = value; 675 } 676 677 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface) 678 { 679 json["BMCState"] = lastSegment('.', dataIface.getBMCState()); 680 json["ChassisState"] = lastSegment('.', dataIface.getChassisState()); 681 json["HostState"] = lastSegment('.', dataIface.getHostState()); 682 json["BootState"] = lastSegment('.', dataIface.getBootState()); 683 } 684 685 std::unique_ptr<UserData> 686 makeSysInfoUserDataSection(const AdditionalData& ad, 687 const DataInterfaceBase& dataIface) 688 { 689 nlohmann::json json; 690 691 addProcessNameToJSON(json, ad.getValue("_PID"), dataIface); 692 addBMCFWVersionIDToJSON(json, dataIface); 693 addIMKeyword(json, dataIface); 694 addStatesToJSON(json, dataIface); 695 696 return makeJSONUserDataSection(json); 697 } 698 699 std::vector<uint8_t> readFD(int fd) 700 { 701 std::vector<uint8_t> data; 702 703 // Get the size 704 struct stat s; 705 int r = fstat(fd, &s); 706 if (r != 0) 707 { 708 auto e = errno; 709 log<level::ERR>("Could not get FFDC file size from FD", 710 entry("ERRNO=%d", e)); 711 return data; 712 } 713 714 if (0 == s.st_size) 715 { 716 log<level::ERR>("FFDC file is empty"); 717 return data; 718 } 719 720 data.resize(s.st_size); 721 722 // Make sure its at the beginning, as maybe another 723 // extension already used it. 724 r = lseek(fd, 0, SEEK_SET); 725 if (r == -1) 726 { 727 auto e = errno; 728 log<level::ERR>("Could not seek to beginning of FFDC file", 729 entry("ERRNO=%d", e)); 730 return data; 731 } 732 733 r = read(fd, data.data(), s.st_size); 734 if (r == -1) 735 { 736 auto e = errno; 737 log<level::ERR>("Could not read FFDC file", entry("ERRNO=%d", e)); 738 } 739 else if (r != s.st_size) 740 { 741 log<level::WARNING>("Could not read full FFDC file", 742 entry("FILE_SIZE=%d", s.st_size), 743 entry("SIZE_READ=%d", r)); 744 } 745 746 return data; 747 } 748 749 std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID, 750 const PelFFDCfile& file) 751 { 752 auto data = readFD(file.fd); 753 754 if (data.empty()) 755 { 756 return std::unique_ptr<UserData>(); 757 } 758 759 // The data needs 4 Byte alignment, and save amount padded for the 760 // CBOR case. 761 uint32_t pad = 0; 762 while (data.size() % 4) 763 { 764 data.push_back(0); 765 pad++; 766 } 767 768 // For JSON, CBOR, and Text use our component ID, subType, and version, 769 // otherwise use the supplied ones. 770 uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging); 771 uint8_t subType{}; 772 uint8_t version{}; 773 774 switch (file.format) 775 { 776 case UserDataFormat::json: 777 subType = static_cast<uint8_t>(UserDataFormat::json); 778 version = static_cast<uint8_t>(UserDataFormatVersion::json); 779 break; 780 case UserDataFormat::cbor: 781 subType = static_cast<uint8_t>(UserDataFormat::cbor); 782 version = static_cast<uint8_t>(UserDataFormatVersion::cbor); 783 784 // The CBOR parser will fail on the extra pad bytes since they 785 // aren't CBOR. Add the amount we padded to the end and other 786 // code will remove it all before parsing. 787 { 788 data.resize(data.size() + 4); 789 Stream stream{data}; 790 stream.offset(data.size() - 4); 791 stream << pad; 792 } 793 794 break; 795 case UserDataFormat::text: 796 subType = static_cast<uint8_t>(UserDataFormat::text); 797 version = static_cast<uint8_t>(UserDataFormatVersion::text); 798 break; 799 case UserDataFormat::custom: 800 default: 801 // Use the passed in values 802 compID = componentID; 803 subType = file.subType; 804 version = file.version; 805 break; 806 } 807 808 return std::make_unique<UserData>(compID, subType, version, data); 809 } 810 811 } // namespace util 812 813 } // namespace pels 814 } // namespace openpower 815