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