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