1 #include "config.h" 2 3 #include "defines.hpp" 4 #include "ipz_parser.hpp" 5 #include "keyword_vpd_parser.hpp" 6 #include "memory_vpd_parser.hpp" 7 #include "parser_factory.hpp" 8 #include "utils.hpp" 9 #include "vpd_exceptions.hpp" 10 11 #include <assert.h> 12 #include <ctype.h> 13 14 #include <CLI/CLI.hpp> 15 #include <algorithm> 16 #include <cstdarg> 17 #include <exception> 18 #include <filesystem> 19 #include <fstream> 20 #include <gpiod.hpp> 21 #include <iostream> 22 #include <iterator> 23 #include <nlohmann/json.hpp> 24 #include <phosphor-logging/log.hpp> 25 26 using namespace std; 27 using namespace openpower::vpd; 28 using namespace CLI; 29 using namespace vpd::keyword::parser; 30 using namespace openpower::vpd::constants; 31 namespace fs = filesystem; 32 using json = nlohmann::json; 33 using namespace openpower::vpd::parser::factory; 34 using namespace openpower::vpd::inventory; 35 using namespace openpower::vpd::memory::parser; 36 using namespace openpower::vpd::parser::interface; 37 using namespace openpower::vpd::exceptions; 38 using namespace phosphor::logging; 39 40 static const deviceTreeMap deviceTreeSystemTypeMap = { 41 {RAINIER_2U, "conf@aspeed-bmc-ibm-rainier.dtb"}, 42 {RAINIER_4U, "conf@aspeed-bmc-ibm-rainier-4u.dtb"}, 43 {EVEREST, "conf@aspeed-bmc-ibm-everest.dtb"}}; 44 45 /** 46 * @brief Returns the power state for chassis0 47 */ 48 static auto getPowerState() 49 { 50 // TODO: How do we handle multiple chassis? 51 string powerState{}; 52 auto bus = sdbusplus::bus::new_default(); 53 auto properties = 54 bus.new_method_call("xyz.openbmc_project.State.Chassis", 55 "/xyz/openbmc_project/state/chassis0", 56 "org.freedesktop.DBus.Properties", "Get"); 57 properties.append("xyz.openbmc_project.State.Chassis"); 58 properties.append("CurrentPowerState"); 59 auto result = bus.call(properties); 60 if (!result.is_method_error()) 61 { 62 variant<string> val; 63 result.read(val); 64 if (auto pVal = get_if<string>(&val)) 65 { 66 powerState = *pVal; 67 } 68 } 69 cout << "Power state is: " << powerState << endl; 70 return powerState; 71 } 72 73 /** 74 * @brief Expands location codes 75 */ 76 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap, 77 bool isSystemVpd) 78 { 79 auto expanded{unexpanded}; 80 static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard"; 81 static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN"; 82 static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS"; 83 size_t idx = expanded.find("fcs"); 84 try 85 { 86 if (idx != string::npos) 87 { 88 string fc{}; 89 string se{}; 90 if (isSystemVpd) 91 { 92 const auto& fcData = vpdMap.at("VCEN").at("FC"); 93 const auto& seData = vpdMap.at("VCEN").at("SE"); 94 fc = string(fcData.data(), fcData.size()); 95 se = string(seData.data(), seData.size()); 96 } 97 else 98 { 99 fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC"); 100 se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE"); 101 } 102 103 // TODO: See if ND0 can be placed in the JSON 104 expanded.replace(idx, 3, fc.substr(0, 4) + ".ND0." + se); 105 } 106 else 107 { 108 idx = expanded.find("mts"); 109 if (idx != string::npos) 110 { 111 string mt{}; 112 string se{}; 113 if (isSystemVpd) 114 { 115 const auto& mtData = vpdMap.at("VSYS").at("TM"); 116 const auto& seData = vpdMap.at("VSYS").at("SE"); 117 mt = string(mtData.data(), mtData.size()); 118 se = string(seData.data(), seData.size()); 119 } 120 else 121 { 122 mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM"); 123 se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE"); 124 } 125 126 replace(mt.begin(), mt.end(), '-', '.'); 127 expanded.replace(idx, 3, mt + "." + se); 128 } 129 } 130 } 131 catch (exception& e) 132 { 133 cerr << "Failed to expand location code with exception: " << e.what() 134 << "\n"; 135 } 136 return expanded; 137 } 138 139 /** 140 * @brief Populate FRU specific interfaces. 141 * 142 * This is a common method which handles both 143 * ipz and keyword specific interfaces thus, 144 * reducing the code redundancy. 145 * @param[in] map - Reference to the innermost keyword-value map. 146 * @param[in] preIntrStr - Reference to the interface string. 147 * @param[out] interfaces - Reference to interface map. 148 */ 149 template <typename T> 150 static void populateFruSpecificInterfaces(const T& map, 151 const string& preIntrStr, 152 inventory::InterfaceMap& interfaces) 153 { 154 inventory::PropertyMap prop; 155 156 for (const auto& kwVal : map) 157 { 158 vector<uint8_t> vec(kwVal.second.begin(), kwVal.second.end()); 159 160 auto kw = kwVal.first; 161 162 if (kw[0] == '#') 163 { 164 kw = string("PD_") + kw[1]; 165 } 166 else if (isdigit(kw[0])) 167 { 168 kw = string("N_") + kw; 169 } 170 prop.emplace(move(kw), move(vec)); 171 } 172 173 interfaces.emplace(preIntrStr, move(prop)); 174 } 175 176 /** 177 * @brief Populate Interfaces. 178 * 179 * This method populates common and extra interfaces to dbus. 180 * @param[in] js - json object 181 * @param[out] interfaces - Reference to interface map 182 * @param[in] vpdMap - Reference to the parsed vpd map. 183 * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD. 184 */ 185 template <typename T> 186 static void populateInterfaces(const nlohmann::json& js, 187 inventory::InterfaceMap& interfaces, 188 const T& vpdMap, bool isSystemVpd) 189 { 190 for (const auto& ifs : js.items()) 191 { 192 string inf = ifs.key(); 193 inventory::PropertyMap props; 194 195 for (const auto& itr : ifs.value().items()) 196 { 197 const string& busProp = itr.key(); 198 199 if (itr.value().is_boolean()) 200 { 201 props.emplace(busProp, itr.value().get<bool>()); 202 } 203 else if (itr.value().is_string()) 204 { 205 if constexpr (is_same<T, Parsed>::value) 206 { 207 if (busProp == "LocationCode" && 208 inf == "com.ibm.ipzvpd.Location") 209 { 210 auto prop = expandLocationCode( 211 itr.value().get<string>(), vpdMap, isSystemVpd); 212 props.emplace(busProp, prop); 213 } 214 else 215 { 216 props.emplace(busProp, itr.value().get<string>()); 217 } 218 } 219 else 220 { 221 props.emplace(busProp, itr.value().get<string>()); 222 } 223 } 224 else if (itr.value().is_object()) 225 { 226 const string& rec = itr.value().value("recordName", ""); 227 const string& kw = itr.value().value("keywordName", ""); 228 const string& encoding = itr.value().value("encoding", ""); 229 230 if constexpr (is_same<T, Parsed>::value) 231 { 232 if (!rec.empty() && !kw.empty() && vpdMap.count(rec) && 233 vpdMap.at(rec).count(kw)) 234 { 235 auto encoded = 236 encodeKeyword(vpdMap.at(rec).at(kw), encoding); 237 props.emplace(busProp, encoded); 238 } 239 } 240 else if constexpr (is_same<T, KeywordVpdMap>::value) 241 { 242 if (!kw.empty() && vpdMap.count(kw)) 243 { 244 auto prop = 245 string(vpdMap.at(kw).begin(), vpdMap.at(kw).end()); 246 auto encoded = encodeKeyword(prop, encoding); 247 props.emplace(busProp, encoded); 248 } 249 } 250 } 251 } 252 interfaces.emplace(inf, move(props)); 253 } 254 } 255 256 static Binary getVpdDataInVector(const nlohmann::json& js, const string& file) 257 { 258 uint32_t offset = 0; 259 // check if offset present? 260 for (const auto& item : js["frus"][file]) 261 { 262 if (item.find("offset") != item.end()) 263 { 264 offset = item["offset"]; 265 } 266 } 267 268 // TODO: Figure out a better way to get max possible VPD size. 269 Binary vpdVector; 270 vpdVector.resize(65504); 271 ifstream vpdFile; 272 vpdFile.open(file, ios::binary); 273 274 vpdFile.seekg(offset, ios_base::cur); 275 vpdFile.read(reinterpret_cast<char*>(&vpdVector[0]), 65504); 276 vpdVector.resize(vpdFile.gcount()); 277 278 return vpdVector; 279 } 280 281 /* It does nothing. Just an empty function to return null 282 * at the end of variadic template args 283 */ 284 static string getCommand() 285 { 286 return ""; 287 } 288 289 /* This function to arrange all arguments to make command 290 */ 291 template <typename T, typename... Types> 292 static string getCommand(T arg1, Types... args) 293 { 294 string cmd = " " + arg1 + getCommand(args...); 295 296 return cmd; 297 } 298 299 /* This API takes arguments and run that command 300 * returns output of that command 301 */ 302 template <typename T, typename... Types> 303 static vector<string> executeCmd(T&& path, Types... args) 304 { 305 vector<string> stdOutput; 306 array<char, 128> buffer; 307 308 string cmd = path + getCommand(args...); 309 310 unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose); 311 if (!pipe) 312 { 313 throw runtime_error("popen() failed!"); 314 } 315 while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) 316 { 317 stdOutput.emplace_back(buffer.data()); 318 } 319 320 return stdOutput; 321 } 322 323 /** This API will be called at the end of VPD collection to perform any post 324 * actions. 325 * 326 * @param[in] json - json object 327 * @param[in] file - eeprom file path 328 */ 329 static void postFailAction(const nlohmann::json& json, const string& file) 330 { 331 if ((json["frus"][file].at(0)).find("postActionFail") == 332 json["frus"][file].at(0).end()) 333 { 334 return; 335 } 336 337 uint8_t pinValue = 0; 338 string pinName; 339 340 for (const auto& postAction : 341 (json["frus"][file].at(0))["postActionFail"].items()) 342 { 343 if (postAction.key() == "pin") 344 { 345 pinName = postAction.value(); 346 } 347 else if (postAction.key() == "value") 348 { 349 // Get the value to set 350 pinValue = postAction.value(); 351 } 352 } 353 354 cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl; 355 356 try 357 { 358 gpiod::line outputLine = gpiod::find_line(pinName); 359 360 if (!outputLine) 361 { 362 cout << "Couldn't find output line:" << pinName 363 << " on GPIO. Skipping...\n"; 364 365 return; 366 } 367 outputLine.request( 368 {"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0}, 369 pinValue); 370 } 371 catch (system_error&) 372 { 373 cerr << "Failed to set post-action GPIO" << endl; 374 } 375 } 376 377 /** Performs any pre-action needed to get the FRU setup for collection. 378 * 379 * @param[in] json - json object 380 * @param[in] file - eeprom file path 381 */ 382 static void preAction(const nlohmann::json& json, const string& file) 383 { 384 if ((json["frus"][file].at(0)).find("preAction") == 385 json["frus"][file].at(0).end()) 386 { 387 return; 388 } 389 390 uint8_t pinValue = 0; 391 string pinName; 392 393 for (const auto& postAction : 394 (json["frus"][file].at(0))["preAction"].items()) 395 { 396 if (postAction.key() == "pin") 397 { 398 pinName = postAction.value(); 399 } 400 else if (postAction.key() == "value") 401 { 402 // Get the value to set 403 pinValue = postAction.value(); 404 } 405 } 406 407 cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl; 408 try 409 { 410 gpiod::line outputLine = gpiod::find_line(pinName); 411 412 if (!outputLine) 413 { 414 cout << "Couldn't find output line:" << pinName 415 << " on GPIO. Skipping...\n"; 416 417 return; 418 } 419 outputLine.request( 420 {"FRU pre-action", ::gpiod::line_request::DIRECTION_OUTPUT, 0}, 421 pinValue); 422 } 423 catch (system_error&) 424 { 425 cerr << "Failed to set pre-action GPIO" << endl; 426 return; 427 } 428 429 // Now bind the device 430 string bind = json["frus"][file].at(0).value("bind", ""); 431 cout << "Binding device " << bind << endl; 432 string bindCmd = string("echo \"") + bind + 433 string("\" > /sys/bus/i2c/drivers/at24/bind"); 434 cout << bindCmd << endl; 435 executeCmd(bindCmd); 436 437 // Check if device showed up (test for file) 438 if (!fs::exists(file)) 439 { 440 cout << "EEPROM " << file << " does not exist. Take failure action" 441 << endl; 442 // If not, then take failure postAction 443 postFailAction(json, file); 444 } 445 } 446 447 /** 448 * @brief Prime the Inventory 449 * Prime the inventory by populating only the location code, 450 * type interface and the inventory object for the frus 451 * which are not system vpd fru. 452 * 453 * @param[in] jsObject - Reference to vpd inventory json object 454 * @param[in] vpdMap - Reference to the parsed vpd map 455 * 456 * @returns Map of items in extraInterface. 457 */ 458 template <typename T> 459 inventory::ObjectMap primeInventory(const nlohmann::json& jsObject, 460 const T& vpdMap) 461 { 462 inventory::ObjectMap objects; 463 464 for (auto& itemFRUS : jsObject["frus"].items()) 465 { 466 // Take pre actions 467 preAction(jsObject, itemFRUS.key()); 468 for (auto& itemEEPROM : itemFRUS.value()) 469 { 470 inventory::InterfaceMap interfaces; 471 auto isSystemVpd = itemEEPROM.value("isSystemVpd", false); 472 inventory::Object object(itemEEPROM.at("inventoryPath")); 473 474 if (!isSystemVpd && !itemEEPROM.value("noprime", false)) 475 { 476 inventory::PropertyMap presProp; 477 presProp.emplace("Present", false); 478 interfaces.emplace("xyz.openbmc_project.Inventory.Item", 479 move(presProp)); 480 481 if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end()) 482 { 483 for (const auto& eI : itemEEPROM["extraInterfaces"].items()) 484 { 485 inventory::PropertyMap props; 486 if (eI.key() == 487 openpower::vpd::constants::LOCATION_CODE_INF) 488 { 489 if constexpr (std::is_same<T, Parsed>::value) 490 { 491 for (auto& lC : eI.value().items()) 492 { 493 auto propVal = expandLocationCode( 494 lC.value().get<string>(), vpdMap, true); 495 496 props.emplace(move(lC.key()), 497 move(propVal)); 498 interfaces.emplace(move(eI.key()), 499 move(props)); 500 } 501 } 502 } 503 else if (eI.key().find("Inventory.Item.") != 504 string::npos) 505 { 506 interfaces.emplace(move(eI.key()), move(props)); 507 } 508 } 509 } 510 objects.emplace(move(object), move(interfaces)); 511 } 512 } 513 } 514 return objects; 515 } 516 517 /** 518 * @brief This API executes command to set environment variable 519 * And then reboot the system 520 * @param[in] key -env key to set new value 521 * @param[in] value -value to set. 522 */ 523 void setEnvAndReboot(const string& key, const string& value) 524 { 525 // set env and reboot and break. 526 executeCmd("/sbin/fw_setenv", key, value); 527 log<level::INFO>("Rebooting BMC to pick up new device tree"); 528 // make dbus call to reboot 529 auto bus = sdbusplus::bus::new_default_system(); 530 auto method = bus.new_method_call( 531 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 532 "org.freedesktop.systemd1.Manager", "Reboot"); 533 bus.call_noreply(method); 534 } 535 536 /* 537 * @brief This API checks for env var fitconfig. 538 * If not initialised OR updated as per the current system type, 539 * update this env var and reboot the system. 540 * 541 * @param[in] systemType IM kwd in vpd tells about which system type it is. 542 * */ 543 void setDevTreeEnv(const string& systemType) 544 { 545 string newDeviceTree; 546 547 if (deviceTreeSystemTypeMap.find(systemType) != 548 deviceTreeSystemTypeMap.end()) 549 { 550 newDeviceTree = deviceTreeSystemTypeMap.at(systemType); 551 } 552 553 string readVarValue; 554 bool envVarFound = false; 555 556 vector<string> output = executeCmd("/sbin/fw_printenv"); 557 for (const auto& entry : output) 558 { 559 size_t pos = entry.find("="); 560 string key = entry.substr(0, pos); 561 if (key != "fitconfig") 562 { 563 continue; 564 } 565 566 envVarFound = true; 567 if (pos + 1 < entry.size()) 568 { 569 readVarValue = entry.substr(pos + 1); 570 if (readVarValue.find(newDeviceTree) != string::npos) 571 { 572 // fitconfig is Updated. No action needed 573 break; 574 } 575 } 576 // set env and reboot and break. 577 setEnvAndReboot(key, newDeviceTree); 578 exit(0); 579 } 580 581 // check If env var Not found 582 if (!envVarFound) 583 { 584 setEnvAndReboot("fitconfig", newDeviceTree); 585 } 586 } 587 588 /** 589 * @brief API to call VPD manager to write VPD to EEPROM. 590 * @param[in] Object path. 591 * @param[in] record to be updated. 592 * @param[in] keyword to be updated. 593 * @param[in] keyword data to be updated 594 */ 595 void updateHardware(const string& objectName, const string& recName, 596 const string& kwdName, const Binary& data) 597 { 598 try 599 { 600 auto bus = sdbusplus::bus::new_default(); 601 auto properties = 602 bus.new_method_call(BUSNAME, OBJPATH, IFACE, "WriteKeyword"); 603 properties.append( 604 static_cast<sdbusplus::message::object_path>(objectName)); 605 properties.append(recName); 606 properties.append(kwdName); 607 properties.append(data); 608 bus.call(properties); 609 } 610 catch (const sdbusplus::exception::SdBusError& e) 611 { 612 std::string what = 613 "VPDManager WriteKeyword api failed for inventory path " + 614 objectName; 615 what += " record " + recName; 616 what += " keyword " + kwdName; 617 what += " with bus error = " + std::string(e.what()); 618 619 // map to hold additional data in case of logging pel 620 PelAdditionalData additionalData{}; 621 additionalData.emplace("CALLOUT_INVENTORY_PATH", objectName); 622 additionalData.emplace("DESCRIPTION", what); 623 createPEL(additionalData, errIntfForBusFailure); 624 } 625 } 626 627 /** 628 * @brief API to check if we need to restore system VPD 629 * This functionality is only applicable for IPZ VPD data. 630 * @param[in] vpdMap - IPZ vpd map 631 * @param[in] objectPath - Object path for the FRU 632 * @return EEPROMs with records and keywords updated at standby 633 */ 634 std::vector<RestoredEeproms> restoreSystemVPD(Parsed& vpdMap, 635 const string& objectPath) 636 { 637 // the list of keywords for VSYS record is as per the S0 system. Should be 638 // updated for another type of systems 639 static std::unordered_map<std::string, std::vector<std::string>> svpdKwdMap{ 640 {"VSYS", {"BR", "TM", "SE", "SU", "RB"}}, 641 {"VCEN", {"FC", "SE"}}, 642 {"LXR0", {"LX"}}}; 643 644 // vector to hold all the EEPROMs updated at standby 645 std::vector<RestoredEeproms> updatedEeproms = {}; 646 647 for (const auto& systemRecKwdPair : svpdKwdMap) 648 { 649 auto it = vpdMap.find(systemRecKwdPair.first); 650 651 // check if record is found in map we got by parser 652 if (it != vpdMap.end()) 653 { 654 const auto& kwdListForRecord = systemRecKwdPair.second; 655 for (const auto& keyword : kwdListForRecord) 656 { 657 DbusPropertyMap& kwdValMap = it->second; 658 auto iterator = kwdValMap.find(keyword); 659 660 if (iterator != kwdValMap.end()) 661 { 662 string& kwdValue = iterator->second; 663 664 // check bus data 665 const string& recordName = systemRecKwdPair.first; 666 const string& busValue = readBusProperty( 667 objectPath, ipzVpdInf + recordName, keyword); 668 669 if (busValue.find_first_not_of(' ') != string::npos) 670 { 671 if (kwdValue.find_first_not_of(' ') != string::npos) 672 { 673 // both the data are present, check for mismatch 674 if (busValue != kwdValue) 675 { 676 string errMsg = "VPD data mismatch on cache " 677 "and hardware for record: "; 678 errMsg += (*it).first; 679 errMsg += " and keyword: "; 680 errMsg += keyword; 681 682 // data mismatch 683 PelAdditionalData additionalData; 684 additionalData.emplace("CALLOUT_INVENTORY_PATH", 685 objectPath); 686 687 additionalData.emplace("DESCRIPTION", errMsg); 688 689 createPEL(additionalData, errIntfForInvalidVPD); 690 } 691 } 692 else 693 { 694 // implies hardware data is blank 695 // update the map 696 Binary busData(busValue.begin(), busValue.end()); 697 698 updatedEeproms.push_back(std::make_tuple( 699 objectPath, recordName, keyword, busData)); 700 } 701 702 // update the map as well, so that cache data is not 703 // updated as blank while populating VPD map on Dbus in 704 // populateDBus Api 705 kwdValue = busValue; 706 continue; 707 } 708 else if (kwdValue.find_first_not_of(' ') == string::npos) 709 { 710 string errMsg = "VPD is blank on both cache and " 711 "hardware for record: "; 712 errMsg += (*it).first; 713 errMsg += " and keyword: "; 714 errMsg += keyword; 715 errMsg += ". SSR need to update hardware VPD."; 716 717 // both the data are blanks, log PEL 718 PelAdditionalData additionalData; 719 additionalData.emplace("CALLOUT_INVENTORY_PATH", 720 objectPath); 721 722 additionalData.emplace("DESCRIPTION", errMsg); 723 724 // log PEL TODO: Block IPL 725 createPEL(additionalData, errIntfForBlankSystemVPD); 726 continue; 727 } 728 } 729 } 730 } 731 } 732 733 return updatedEeproms; 734 } 735 736 /** 737 * @brief Populate Dbus. 738 * This method invokes all the populateInterface functions 739 * and notifies PIM about dbus object. 740 * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the 741 * input. 742 * @param[in] js - Inventory json object 743 * @param[in] filePath - Path of the vpd file 744 * @param[in] preIntrStr - Interface string 745 */ 746 template <typename T> 747 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath) 748 { 749 inventory::InterfaceMap interfaces; 750 inventory::ObjectMap objects; 751 inventory::PropertyMap prop; 752 753 // map to hold all the keywords whose value has been changed at standby 754 vector<RestoredEeproms> updatedEeproms = {}; 755 756 bool isSystemVpd = false; 757 for (const auto& item : js["frus"][filePath]) 758 { 759 const auto& objectPath = item["inventoryPath"]; 760 sdbusplus::message::object_path object(objectPath); 761 isSystemVpd = item.value("isSystemVpd", false); 762 763 // Populate the VPD keywords and the common interfaces only if we 764 // are asked to inherit that data from the VPD, else only add the 765 // extraInterfaces. 766 if (item.value("inherit", true)) 767 { 768 if constexpr (is_same<T, Parsed>::value) 769 { 770 if (isSystemVpd) 771 { 772 std::vector<std::string> interfaces = { 773 motherBoardInterface}; 774 // call mapper to check for object path creation 775 MapperResponse subTree = 776 getObjectSubtreeForInterfaces(pimPath, 0, interfaces); 777 778 // Skip system vpd restore if object path is not generated 779 // for motherboard, Implies first boot. 780 if (subTree.size() != 0) 781 { 782 assert( 783 (subTree.find(pimPath + std::string(objectPath)) != 784 subTree.end())); 785 786 updatedEeproms = restoreSystemVPD(vpdMap, objectPath); 787 } 788 else 789 { 790 log<level::ERR>("No object path found"); 791 } 792 } 793 794 // Each record in the VPD becomes an interface and all 795 // keyword within the record are properties under that 796 // interface. 797 for (const auto& record : vpdMap) 798 { 799 populateFruSpecificInterfaces( 800 record.second, ipzVpdInf + record.first, interfaces); 801 } 802 } 803 else if constexpr (is_same<T, KeywordVpdMap>::value) 804 { 805 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces); 806 } 807 if (js.find("commonInterfaces") != js.end()) 808 { 809 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, 810 isSystemVpd); 811 } 812 } 813 else 814 { 815 // Check if we have been asked to inherit specific record(s) 816 if constexpr (is_same<T, Parsed>::value) 817 { 818 if (item.find("copyRecords") != item.end()) 819 { 820 for (const auto& record : item["copyRecords"]) 821 { 822 const string& recordName = record; 823 if (vpdMap.find(recordName) != vpdMap.end()) 824 { 825 populateFruSpecificInterfaces( 826 vpdMap.at(recordName), ipzVpdInf + recordName, 827 interfaces); 828 } 829 } 830 } 831 } 832 } 833 if (item.value("inheritEI", true)) 834 { 835 // Populate interfaces and properties that are common to every FRU 836 // and additional interface that might be defined on a per-FRU 837 // basis. 838 if (item.find("extraInterfaces") != item.end()) 839 { 840 populateInterfaces(item["extraInterfaces"], interfaces, vpdMap, 841 isSystemVpd); 842 } 843 } 844 objects.emplace(move(object), move(interfaces)); 845 } 846 847 if (isSystemVpd) 848 { 849 vector<uint8_t> imVal; 850 if constexpr (is_same<T, Parsed>::value) 851 { 852 auto property = vpdMap.find("VSBP"); 853 if (property != vpdMap.end()) 854 { 855 auto value = (property->second).find("IM"); 856 if (value != (property->second).end()) 857 { 858 copy(value->second.begin(), value->second.end(), 859 back_inserter(imVal)); 860 } 861 } 862 } 863 864 fs::path target; 865 fs::path link = INVENTORY_JSON_SYM_LINK; 866 867 ostringstream oss; 868 for (auto& i : imVal) 869 { 870 oss << setw(2) << setfill('0') << hex << static_cast<int>(i); 871 } 872 string imValStr = oss.str(); 873 874 if (imValStr == RAINIER_4U) // 4U 875 { 876 target = INVENTORY_JSON_4U; 877 } 878 else if (imValStr == RAINIER_2U) // 2U 879 { 880 target = INVENTORY_JSON_2U; 881 } 882 else if (imValStr == EVEREST) 883 { 884 target = INVENTORY_JSON_EVEREST; 885 } 886 887 // Create the directory for hosting the symlink 888 fs::create_directories(VPD_FILES_PATH); 889 // unlink the symlink previously created (if any) 890 remove(INVENTORY_JSON_SYM_LINK); 891 // create a new symlink based on the system 892 fs::create_symlink(target, link); 893 894 // Reloading the json 895 ifstream inventoryJson(link); 896 auto js = json::parse(inventoryJson); 897 inventoryJson.close(); 898 899 inventory::ObjectMap primeObject = primeInventory(js, vpdMap); 900 objects.insert(primeObject.begin(), primeObject.end()); 901 902 // set the U-boot environment variable for device-tree 903 setDevTreeEnv(imValStr); 904 905 // if system VPD has been restored at standby, update the EEPROM 906 for (const auto& item : updatedEeproms) 907 { 908 updateHardware(get<0>(item), get<1>(item), get<2>(item), 909 get<3>(item)); 910 } 911 } 912 913 // Notify PIM 914 inventory::callPIM(move(objects)); 915 } 916 917 int main(int argc, char** argv) 918 { 919 int rc = 0; 920 string file{}; 921 json js{}; 922 923 // map to hold additional data in case of logging pel 924 PelAdditionalData additionalData{}; 925 926 // this is needed to hold base fru inventory path in case there is ECC or 927 // vpd exception while parsing the file 928 std::string baseFruInventoryPath = {}; 929 930 try 931 { 932 App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store " 933 "in DBUS"}; 934 string file{}; 935 936 app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") 937 ->required(); 938 939 CLI11_PARSE(app, argc, argv); 940 941 auto jsonToParse = INVENTORY_JSON_DEFAULT; 942 943 // If the symlink exists, it means it has been setup for us, switch the 944 // path 945 if (fs::exists(INVENTORY_JSON_SYM_LINK)) 946 { 947 jsonToParse = INVENTORY_JSON_SYM_LINK; 948 } 949 950 // Make sure that the file path we get is for a supported EEPROM 951 ifstream inventoryJson(jsonToParse); 952 if (!inventoryJson) 953 { 954 throw( 955 (VpdJsonException("Failed to access Json path", jsonToParse))); 956 } 957 958 try 959 { 960 js = json::parse(inventoryJson); 961 } 962 catch (json::parse_error& ex) 963 { 964 throw((VpdJsonException("Json parsing failed", jsonToParse))); 965 } 966 967 if ((js.find("frus") == js.end()) || 968 (js["frus"].find(file) == js["frus"].end())) 969 { 970 cout << "Device path not in JSON, ignoring" << endl; 971 return 0; 972 } 973 974 if (!fs::exists(file)) 975 { 976 cout << "Device path: " << file 977 << " does not exist. Spurious udev event? Exiting." << endl; 978 return 0; 979 } 980 981 baseFruInventoryPath = js["frus"][file][0]["inventoryPath"]; 982 // Check if we can read the VPD file based on the power state 983 if (js["frus"][file].at(0).value("powerOffOnly", false)) 984 { 985 if ("xyz.openbmc_project.State.Chassis.PowerState.On" == 986 getPowerState()) 987 { 988 cout << "This VPD cannot be read when power is ON" << endl; 989 return 0; 990 } 991 } 992 993 try 994 { 995 Binary vpdVector = getVpdDataInVector(js, file); 996 ParserInterface* parser = ParserFactory::getParser(move(vpdVector)); 997 998 variant<KeywordVpdMap, Store> parseResult; 999 parseResult = parser->parse(); 1000 if (auto pVal = get_if<Store>(&parseResult)) 1001 { 1002 populateDbus(pVal->getVpdMap(), js, file); 1003 } 1004 else if (auto pVal = get_if<KeywordVpdMap>(&parseResult)) 1005 { 1006 populateDbus(*pVal, js, file); 1007 } 1008 1009 // release the parser object 1010 ParserFactory::freeParser(parser); 1011 } 1012 catch (exception& e) 1013 { 1014 postFailAction(js, file); 1015 throw e; 1016 } 1017 } 1018 catch (const VpdJsonException& ex) 1019 { 1020 additionalData.emplace("JSON_PATH", ex.getJsonPath()); 1021 additionalData.emplace("DESCRIPTION", ex.what()); 1022 createPEL(additionalData, errIntfForJsonFailure); 1023 1024 cerr << ex.what() << "\n"; 1025 rc = -1; 1026 } 1027 catch (const VpdEccException& ex) 1028 { 1029 additionalData.emplace("DESCRIPTION", "ECC check failed"); 1030 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1031 INVENTORY_PATH + baseFruInventoryPath); 1032 createPEL(additionalData, errIntfForEccCheckFail); 1033 1034 cerr << ex.what() << "\n"; 1035 rc = -1; 1036 } 1037 catch (const VpdDataException& ex) 1038 { 1039 additionalData.emplace("DESCRIPTION", "Invalid VPD data"); 1040 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1041 INVENTORY_PATH + baseFruInventoryPath); 1042 createPEL(additionalData, errIntfForInvalidVPD); 1043 1044 cerr << ex.what() << "\n"; 1045 rc = -1; 1046 } 1047 catch (exception& e) 1048 { 1049 cerr << e.what() << "\n"; 1050 rc = -1; 1051 } 1052 1053 return rc; 1054 } 1055