1 #include "config.h" 2 3 #include "common_utility.hpp" 4 #include "defines.hpp" 5 #include "editor_impl.hpp" 6 #include "ibm_vpd_utils.hpp" 7 #include "ipz_parser.hpp" 8 #include "keyword_vpd_parser.hpp" 9 #include "memory_vpd_parser.hpp" 10 #include "parser_factory.hpp" 11 #include "vpd_exceptions.hpp" 12 13 #include <assert.h> 14 #include <ctype.h> 15 16 #include <CLI/CLI.hpp> 17 #include <boost/algorithm/string.hpp> 18 #include <gpiod.hpp> 19 #include <phosphor-logging/log.hpp> 20 21 #include <algorithm> 22 #include <cstdarg> 23 #include <exception> 24 #include <filesystem> 25 #include <fstream> 26 #include <iostream> 27 #include <iterator> 28 #include <regex> 29 #include <thread> 30 31 using namespace std; 32 using namespace openpower::vpd; 33 using namespace CLI; 34 using namespace vpd::keyword::parser; 35 using namespace openpower::vpd::constants; 36 namespace fs = filesystem; 37 using json = nlohmann::json; 38 using namespace openpower::vpd::parser::factory; 39 using namespace openpower::vpd::inventory; 40 using namespace openpower::vpd::memory::parser; 41 using namespace openpower::vpd::parser::interface; 42 using namespace openpower::vpd::exceptions; 43 using namespace phosphor::logging; 44 using namespace openpower::vpd::manager::editor; 45 46 /** 47 * @brief API declaration, Populate Dbus. 48 * 49 * This method invokes all the populateInterface functions 50 * and notifies PIM about dbus object. 51 * 52 * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the 53 * input. 54 * @param[in] js - Inventory json object 55 * @param[in] filePath - Path of the vpd file 56 * @param[in] preIntrStr - Interface string 57 */ 58 template <typename T> 59 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath); 60 61 /** 62 * @brief Returns the BMC state 63 */ 64 static auto getBMCState() 65 { 66 std::string bmcState; 67 try 68 { 69 auto bus = sdbusplus::bus::new_default(); 70 auto properties = bus.new_method_call( 71 "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0", 72 "org.freedesktop.DBus.Properties", "Get"); 73 properties.append("xyz.openbmc_project.State.BMC"); 74 properties.append("CurrentBMCState"); 75 auto result = bus.call(properties); 76 std::variant<std::string> val; 77 result.read(val); 78 if (auto pVal = std::get_if<std::string>(&val)) 79 { 80 bmcState = *pVal; 81 } 82 } 83 catch (const sdbusplus::exception::SdBusError& e) 84 { 85 // Ignore any error 86 std::cerr << "Failed to get BMC state: " << e.what() << "\n"; 87 } 88 return bmcState; 89 } 90 91 /** 92 * @brief Check if the FRU is in the cache 93 * 94 * Checks if the FRU associated with the supplied D-Bus object path is already 95 * on D-Bus. This can be used to test if a VPD collection is required for this 96 * FRU. It uses the "xyz.openbmc_project.Inventory.Item, Present" property to 97 * determine the presence of a FRU in the cache. 98 * 99 * @param objectPath - The D-Bus object path without the PIM prefix. 100 * @return true if the object exists on D-Bus, false otherwise. 101 */ 102 static auto isFruInVpdCache(const std::string& objectPath) 103 { 104 try 105 { 106 auto bus = sdbusplus::bus::new_default(); 107 auto invPath = std::string{pimPath} + objectPath; 108 auto props = bus.new_method_call( 109 "xyz.openbmc_project.Inventory.Manager", invPath.c_str(), 110 "org.freedesktop.DBus.Properties", "Get"); 111 props.append("xyz.openbmc_project.Inventory.Item"); 112 props.append("Present"); 113 auto result = bus.call(props); 114 std::variant<bool> present; 115 result.read(present); 116 if (auto pVal = std::get_if<bool>(&present)) 117 { 118 return *pVal; 119 } 120 return false; 121 } 122 catch (const sdbusplus::exception::SdBusError& e) 123 { 124 std::cout << "FRU: " << objectPath << " not in D-Bus\n"; 125 // Assume not present in case of an error 126 return false; 127 } 128 } 129 130 /** 131 * @brief Check if VPD recollection is needed for the given EEPROM 132 * 133 * Not all FRUs can be swapped at BMC ready state. This function does the 134 * following: 135 * -- Check if the FRU is marked as "pluggableAtStandby" OR 136 * "concurrentlyMaintainable". If so, return true. 137 * -- Check if we are at BMC NotReady state. If we are, then return true. 138 * -- Else check if the FRU is not present in the VPD cache (to cover for VPD 139 * force collection). If not found in the cache, return true. 140 * -- Else return false. 141 * 142 * @param js - JSON Object. 143 * @param filePath - The EEPROM file. 144 * @return true if collection should be attempted, false otherwise. 145 */ 146 static auto needsRecollection(const nlohmann::json& js, const string& filePath) 147 { 148 if (js["frus"][filePath].at(0).value("pluggableAtStandby", false) || 149 js["frus"][filePath].at(0).value("concurrentlyMaintainable", false)) 150 { 151 return true; 152 } 153 if (getBMCState() == "xyz.openbmc_project.State.BMC.BMCState.NotReady") 154 { 155 return true; 156 } 157 if (!isFruInVpdCache(js["frus"][filePath].at(0).value("inventoryPath", ""))) 158 { 159 return true; 160 } 161 return false; 162 } 163 164 /** 165 * @brief Expands location codes 166 */ 167 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap, 168 bool isSystemVpd) 169 { 170 auto expanded{unexpanded}; 171 static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard"; 172 static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN"; 173 static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS"; 174 size_t idx = expanded.find("fcs"); 175 try 176 { 177 if (idx != string::npos) 178 { 179 string fc{}; 180 string se{}; 181 if (isSystemVpd) 182 { 183 const auto& fcData = vpdMap.at("VCEN").at("FC"); 184 const auto& seData = vpdMap.at("VCEN").at("SE"); 185 fc = string(fcData.data(), fcData.size()); 186 se = string(seData.data(), seData.size()); 187 } 188 else 189 { 190 fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC"); 191 se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE"); 192 } 193 194 // TODO: See if ND0 can be placed in the JSON 195 expanded.replace(idx, 3, fc.substr(0, 4) + ".ND0." + se); 196 } 197 else 198 { 199 idx = expanded.find("mts"); 200 if (idx != string::npos) 201 { 202 string mt{}; 203 string se{}; 204 if (isSystemVpd) 205 { 206 const auto& mtData = vpdMap.at("VSYS").at("TM"); 207 const auto& seData = vpdMap.at("VSYS").at("SE"); 208 mt = string(mtData.data(), mtData.size()); 209 se = string(seData.data(), seData.size()); 210 } 211 else 212 { 213 mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM"); 214 se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE"); 215 } 216 217 replace(mt.begin(), mt.end(), '-', '.'); 218 expanded.replace(idx, 3, mt + "." + se); 219 } 220 } 221 } 222 catch (const exception& e) 223 { 224 std::cerr << "Failed to expand location code with exception: " 225 << e.what() << "\n"; 226 } 227 return expanded; 228 } 229 230 /** 231 * @brief Populate FRU specific interfaces. 232 * 233 * This is a common method which handles both 234 * ipz and keyword specific interfaces thus, 235 * reducing the code redundancy. 236 * @param[in] map - Reference to the innermost keyword-value map. 237 * @param[in] preIntrStr - Reference to the interface string. 238 * @param[out] interfaces - Reference to interface map. 239 */ 240 template <typename T> 241 static void populateFruSpecificInterfaces(const T& map, 242 const string& preIntrStr, 243 inventory::InterfaceMap& interfaces) 244 { 245 inventory::PropertyMap prop; 246 247 for (const auto& kwVal : map) 248 { 249 auto kw = kwVal.first; 250 251 if (kw[0] == '#') 252 { 253 kw = string("PD_") + kw[1]; 254 } 255 else if (isdigit(kw[0])) 256 { 257 kw = string("N_") + kw; 258 } 259 if constexpr (is_same<T, KeywordVpdMap>::value) 260 { 261 if (auto keywordValue = get_if<Binary>(&kwVal.second)) 262 { 263 Binary vec((*keywordValue).begin(), (*keywordValue).end()); 264 prop.emplace(move(kw), move(vec)); 265 } 266 else if (auto keywordValue = get_if<std::string>(&kwVal.second)) 267 { 268 Binary vec((*keywordValue).begin(), (*keywordValue).end()); 269 prop.emplace(move(kw), move(vec)); 270 } 271 else if (auto keywordValue = get_if<size_t>(&kwVal.second)) 272 { 273 if (kw == "MemorySizeInKB") 274 { 275 inventory::PropertyMap memProp; 276 memProp.emplace(move(kw), ((*keywordValue))); 277 interfaces.emplace( 278 "xyz.openbmc_project.Inventory.Item.Dimm", 279 move(memProp)); 280 } 281 else 282 { 283 std::cerr << "Unknown Keyword[" << kw << "] found "; 284 } 285 } 286 else 287 { 288 std::cerr << "Unknown Variant found "; 289 } 290 } 291 else 292 { 293 Binary vec(kwVal.second.begin(), kwVal.second.end()); 294 prop.emplace(move(kw), move(vec)); 295 } 296 } 297 298 interfaces.emplace(preIntrStr, move(prop)); 299 } 300 301 /** 302 * @brief Populate Interfaces. 303 * 304 * This method populates common and extra interfaces to dbus. 305 * @param[in] js - json object 306 * @param[out] interfaces - Reference to interface map 307 * @param[in] vpdMap - Reference to the parsed vpd map. 308 * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD. 309 */ 310 template <typename T> 311 static void populateInterfaces(const nlohmann::json& js, 312 inventory::InterfaceMap& interfaces, 313 const T& vpdMap, bool isSystemVpd) 314 { 315 for (const auto& ifs : js.items()) 316 { 317 string inf = ifs.key(); 318 inventory::PropertyMap props; 319 320 for (const auto& itr : ifs.value().items()) 321 { 322 const string& busProp = itr.key(); 323 324 if (itr.value().is_boolean()) 325 { 326 props.emplace(busProp, itr.value().get<bool>()); 327 } 328 else if (itr.value().is_string()) 329 { 330 if (busProp == "LocationCode" && inf == IBM_LOCATION_CODE_INF) 331 { 332 std::string prop; 333 if constexpr (is_same<T, Parsed>::value) 334 { 335 // TODO deprecate the com.ibm interface later 336 prop = expandLocationCode(itr.value().get<string>(), 337 vpdMap, isSystemVpd); 338 } 339 else if constexpr (is_same<T, KeywordVpdMap>::value) 340 { 341 // Send empty Parsed object to expandLocationCode api. 342 prop = expandLocationCode(itr.value().get<string>(), 343 Parsed{}, false); 344 } 345 props.emplace(busProp, prop); 346 interfaces.emplace(XYZ_LOCATION_CODE_INF, props); 347 interfaces.emplace(IBM_LOCATION_CODE_INF, props); 348 } 349 else 350 { 351 props.emplace(busProp, itr.value().get<string>()); 352 } 353 } 354 else if (itr.value().is_array()) 355 { 356 try 357 { 358 props.emplace(busProp, itr.value().get<Binary>()); 359 } 360 catch (const nlohmann::detail::type_error& e) 361 { 362 std::cerr << "Type exception: " << e.what() << "\n"; 363 // Ignore any type errors 364 } 365 } 366 else if (itr.value().is_object()) 367 { 368 const string& rec = itr.value().value("recordName", ""); 369 const string& kw = itr.value().value("keywordName", ""); 370 const string& encoding = itr.value().value("encoding", ""); 371 372 if constexpr (is_same<T, Parsed>::value) 373 { 374 if (!rec.empty() && !kw.empty() && vpdMap.count(rec) && 375 vpdMap.at(rec).count(kw)) 376 { 377 auto encoded = encodeKeyword(vpdMap.at(rec).at(kw), 378 encoding); 379 props.emplace(busProp, encoded); 380 } 381 } 382 else if constexpr (is_same<T, KeywordVpdMap>::value) 383 { 384 if (!kw.empty() && vpdMap.count(kw)) 385 { 386 if (auto kwValue = get_if<Binary>(&vpdMap.at(kw))) 387 { 388 auto prop = string((*kwValue).begin(), 389 (*kwValue).end()); 390 391 auto encoded = encodeKeyword(prop, encoding); 392 393 props.emplace(busProp, encoded); 394 } 395 else if (auto kwValue = 396 get_if<std::string>(&vpdMap.at(kw))) 397 { 398 auto prop = string((*kwValue).begin(), 399 (*kwValue).end()); 400 401 auto encoded = encodeKeyword(prop, encoding); 402 403 props.emplace(busProp, encoded); 404 } 405 else if (auto uintValue = 406 get_if<size_t>(&vpdMap.at(kw))) 407 { 408 props.emplace(busProp, *uintValue); 409 } 410 else 411 { 412 std::cerr << " Unknown Keyword [" << kw 413 << "] Encountered"; 414 } 415 } 416 } 417 } 418 else if (itr.value().is_number()) 419 { 420 // For now assume the value is a size_t. In the future it would 421 // be nice to come up with a way to get the type from the JSON. 422 props.emplace(busProp, itr.value().get<size_t>()); 423 } 424 } 425 insertOrMerge(interfaces, inf, move(props)); 426 } 427 } 428 429 /** 430 * @brief This API checks if this FRU is pcie_devices. If yes then it further 431 * checks whether it is PASS1 planar. 432 */ 433 static bool isThisPcieOnPass1planar(const nlohmann::json& js, 434 const string& file) 435 { 436 auto isThisPCIeDev = false; 437 auto isPASS1 = false; 438 439 // Check if it is a PCIE device 440 if (js["frus"].find(file) != js["frus"].end()) 441 { 442 if ((js["frus"][file].at(0).find("extraInterfaces") != 443 js["frus"][file].at(0).end())) 444 { 445 if (js["frus"][file].at(0)["extraInterfaces"].find( 446 "xyz.openbmc_project.Inventory.Item.PCIeDevice") != 447 js["frus"][file].at(0)["extraInterfaces"].end()) 448 { 449 isThisPCIeDev = true; 450 } 451 } 452 } 453 454 if (isThisPCIeDev) 455 { 456 // Collect HW version and SystemType to know if it is PASS1 planar. 457 auto bus = sdbusplus::bus::new_default(); 458 auto property1 = bus.new_method_call( 459 INVENTORY_MANAGER_SERVICE, 460 "/xyz/openbmc_project/inventory/system/chassis/motherboard", 461 "org.freedesktop.DBus.Properties", "Get"); 462 property1.append("com.ibm.ipzvpd.VINI"); 463 property1.append("HW"); 464 auto result1 = bus.call(property1); 465 inventory::Value hwVal; 466 result1.read(hwVal); 467 468 // SystemType 469 auto property2 = bus.new_method_call( 470 INVENTORY_MANAGER_SERVICE, 471 "/xyz/openbmc_project/inventory/system/chassis/motherboard", 472 "org.freedesktop.DBus.Properties", "Get"); 473 property2.append("com.ibm.ipzvpd.VSBP"); 474 property2.append("IM"); 475 auto result2 = bus.call(property2); 476 inventory::Value imVal; 477 result2.read(imVal); 478 479 auto pVal1 = get_if<Binary>(&hwVal); 480 auto pVal2 = get_if<Binary>(&imVal); 481 482 if (pVal1 && pVal2) 483 { 484 auto hwVersion = *pVal1; 485 auto systemType = *pVal2; 486 487 // IM kw for Everest 488 Binary everestSystem{80, 00, 48, 00}; 489 490 if (systemType == everestSystem) 491 { 492 if (hwVersion[1] < 21) 493 { 494 isPASS1 = true; 495 } 496 } 497 else if (hwVersion[1] < 2) 498 { 499 isPASS1 = true; 500 } 501 } 502 } 503 504 return (isThisPCIeDev && isPASS1); 505 } 506 507 /** Performs any pre-action needed to get the FRU setup for collection. 508 * 509 * @param[in] json - json object 510 * @param[in] file - eeprom file path 511 */ 512 static void preAction(const nlohmann::json& json, const string& file) 513 { 514 if ((json["frus"][file].at(0)).find("preAction") == 515 json["frus"][file].at(0).end()) 516 { 517 return; 518 } 519 520 try 521 { 522 if (executePreAction(json, file)) 523 { 524 if (json["frus"][file].at(0).find("devAddress") != 525 json["frus"][file].at(0).end()) 526 { 527 // Now bind the device 528 string bind = json["frus"][file].at(0).value("devAddress", ""); 529 std::cout << "Binding device " << bind << std::endl; 530 string bindCmd = string("echo \"") + bind + 531 string("\" > /sys/bus/i2c/drivers/at24/bind"); 532 std::cout << bindCmd << std::endl; 533 executeCmd(bindCmd); 534 535 // Check if device showed up (test for file) 536 if (!fs::exists(file)) 537 { 538 std::cerr << "EEPROM " << file 539 << " does not exist. Take failure action" 540 << std::endl; 541 // If not, then take failure postAction 542 executePostFailAction(json, file); 543 } 544 } 545 else 546 { 547 // missing required informations 548 std::cerr << "VPD inventory JSON missing basic informations of " 549 "preAction " 550 "for this FRU : [" 551 << file << "]. Executing executePostFailAction." 552 << std::endl; 553 554 // Take failure postAction 555 executePostFailAction(json, file); 556 return; 557 } 558 } 559 else 560 { 561 // If the FRU is not there, clear the VINI/CCIN data. 562 // Enity manager probes for this keyword to look for this 563 // FRU, now if the data is persistent on BMC and FRU is 564 // removed this can lead to ambiguity. Hence clearing this 565 // Keyword if FRU is absent. 566 const auto& invPath = 567 json["frus"][file].at(0).value("inventoryPath", ""); 568 569 if (!invPath.empty()) 570 { 571 inventory::ObjectMap pimObjMap{ 572 {invPath, {{"com.ibm.ipzvpd.VINI", {{"CC", Binary{}}}}}}}; 573 574 common::utility::callPIM(move(pimObjMap)); 575 } 576 else 577 { 578 throw std::runtime_error("Path empty in Json"); 579 } 580 } 581 } 582 catch (const GpioException& e) 583 { 584 PelAdditionalData additionalData{}; 585 additionalData.emplace("DESCRIPTION", e.what()); 586 createPEL(additionalData, PelSeverity::WARNING, errIntfForGpioError, 587 nullptr); 588 } 589 } 590 591 /** 592 * @brief Fills the Decorator.AssetTag property into the interfaces map 593 * 594 * This function should only be called in cases where we did not find a JSON 595 * symlink. A missing symlink in /var/lib will be considered as a factory reset 596 * and this function will be used to default the AssetTag property. 597 * 598 * @param interfaces A possibly pre-populated map of inetrfaces to properties. 599 * @param vpdMap A VPD map of the system VPD data. 600 */ 601 static void fillAssetTag(inventory::InterfaceMap& interfaces, 602 const Parsed& vpdMap) 603 { 604 // Read the system serial number and MTM 605 // Default asset tag is Server-MTM-System Serial 606 inventory::Interface assetIntf{ 607 "xyz.openbmc_project.Inventory.Decorator.AssetTag"}; 608 inventory::PropertyMap assetTagProps; 609 std::string defaultAssetTag = 610 std::string{"Server-"} + getKwVal(vpdMap, "VSYS", "TM") + 611 std::string{"-"} + getKwVal(vpdMap, "VSYS", "SE"); 612 assetTagProps.emplace("AssetTag", defaultAssetTag); 613 insertOrMerge(interfaces, assetIntf, std::move(assetTagProps)); 614 } 615 616 /** 617 * @brief Set certain one time properties in the inventory 618 * Use this function to insert the Functional and Enabled properties into the 619 * inventory map. This function first checks if the object in question already 620 * has these properties hosted on D-Bus, if the property is already there, it is 621 * not modified, hence the name "one time". If the property is not already 622 * present, it will be added to the map with a suitable default value (true for 623 * Functional and Enabled) 624 * 625 * @param[in] object - The inventory D-Bus obejct without the inventory prefix. 626 * @param[inout] interfaces - Reference to a map of inventory interfaces to 627 * which the properties will be attached. 628 */ 629 static void setOneTimeProperties(const std::string& object, 630 inventory::InterfaceMap& interfaces) 631 { 632 auto bus = sdbusplus::bus::new_default(); 633 auto objectPath = INVENTORY_PATH + object; 634 auto prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager", 635 objectPath.c_str(), 636 "org.freedesktop.DBus.Properties", "Get"); 637 prop.append("xyz.openbmc_project.State.Decorator.OperationalStatus"); 638 prop.append("Functional"); 639 try 640 { 641 auto result = bus.call(prop); 642 } 643 catch (const sdbusplus::exception::SdBusError& e) 644 { 645 // Treat as property unavailable 646 inventory::PropertyMap prop; 647 prop.emplace("Functional", true); 648 interfaces.emplace( 649 "xyz.openbmc_project.State.Decorator.OperationalStatus", 650 move(prop)); 651 } 652 prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager", 653 objectPath.c_str(), 654 "org.freedesktop.DBus.Properties", "Get"); 655 prop.append("xyz.openbmc_project.Object.Enable"); 656 prop.append("Enabled"); 657 try 658 { 659 auto result = bus.call(prop); 660 } 661 catch (const sdbusplus::exception::SdBusError& e) 662 { 663 // Treat as property unavailable 664 inventory::PropertyMap prop; 665 prop.emplace("Enabled", true); 666 interfaces.emplace("xyz.openbmc_project.Object.Enable", move(prop)); 667 } 668 } 669 670 /** 671 * @brief Prime the Inventory 672 * Prime the inventory by populating only the location code, 673 * type interface and the inventory object for the frus 674 * which are not system vpd fru. 675 * 676 * @param[in] jsObject - Reference to vpd inventory json object 677 * @param[in] vpdMap - Reference to the parsed vpd map 678 * 679 * @returns Map of items in extraInterface. 680 */ 681 template <typename T> 682 inventory::ObjectMap primeInventory(const nlohmann::json& jsObject, 683 const T& vpdMap) 684 { 685 inventory::ObjectMap objects; 686 687 for (auto& itemFRUS : jsObject["frus"].items()) 688 { 689 for (auto& itemEEPROM : itemFRUS.value()) 690 { 691 // Take pre actions if needed 692 if (itemEEPROM.find("preAction") != itemEEPROM.end()) 693 { 694 preAction(jsObject, itemFRUS.key()); 695 } 696 697 inventory::InterfaceMap interfaces; 698 inventory::Object object(itemEEPROM.at("inventoryPath")); 699 700 if ((itemFRUS.key() != systemVpdFilePath) && 701 !itemEEPROM.value("noprime", false)) 702 { 703 inventory::PropertyMap presProp; 704 705 // Do not populate Present property for frus whose 706 // synthesized=true. synthesized=true says the fru VPD is 707 // synthesized and owned by a separate component. 708 // In some cases, the FRU has its own VPD, but still a separate 709 // application handles the FRU's presence. So VPD parser skips 710 // populating Present property by checking the JSON flag, 711 // "handlePresence". 712 if (!itemEEPROM.value("synthesized", false)) 713 { 714 if (itemEEPROM.value("handlePresence", true)) 715 { 716 presProp.emplace("Present", false); 717 interfaces.emplace("xyz.openbmc_project.Inventory.Item", 718 presProp); 719 } 720 } 721 722 setOneTimeProperties(object, interfaces); 723 if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end()) 724 { 725 for (const auto& eI : itemEEPROM["extraInterfaces"].items()) 726 { 727 inventory::PropertyMap props; 728 if (eI.key() == IBM_LOCATION_CODE_INF) 729 { 730 if constexpr (std::is_same<T, Parsed>::value) 731 { 732 for (auto& lC : eI.value().items()) 733 { 734 auto propVal = expandLocationCode( 735 lC.value().get<string>(), vpdMap, true); 736 737 props.emplace(move(lC.key()), 738 move(propVal)); 739 interfaces.emplace(XYZ_LOCATION_CODE_INF, 740 props); 741 interfaces.emplace(move(eI.key()), 742 move(props)); 743 } 744 } 745 } 746 else if (eI.key() == 747 "xyz.openbmc_project.Inventory.Item") 748 { 749 for (auto& val : eI.value().items()) 750 { 751 if (val.key() == "PrettyName") 752 { 753 presProp.emplace(val.key(), 754 val.value().get<string>()); 755 } 756 } 757 // Use insert_or_assign here as we may already have 758 // inserted the present property only earlier in 759 // this function under this same interface. 760 interfaces.insert_or_assign(eI.key(), 761 move(presProp)); 762 } 763 else 764 { 765 interfaces.emplace(move(eI.key()), move(props)); 766 } 767 } 768 } 769 objects.emplace(move(object), move(interfaces)); 770 } 771 } 772 } 773 return objects; 774 } 775 776 /** 777 * @brief This API executes command to set environment variable 778 * And then reboot the system 779 * @param[in] key -env key to set new value 780 * @param[in] value -value to set. 781 */ 782 void setEnvAndReboot(const string& key, const string& value) 783 { 784 // set env and reboot and break. 785 executeCmd("/sbin/fw_setenv", key, value); 786 log<level::INFO>("Rebooting BMC to pick up new device tree"); 787 // make dbus call to reboot 788 auto bus = sdbusplus::bus::new_default_system(); 789 auto method = bus.new_method_call( 790 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 791 "org.freedesktop.systemd1.Manager", "Reboot"); 792 bus.call_noreply(method); 793 } 794 795 /* 796 * @brief This API checks for env var fitconfig. 797 * If not initialised OR updated as per the current system type, 798 * update this env var and reboot the system. 799 * 800 * @param[in] systemType IM kwd in vpd tells about which system type it is. 801 * */ 802 void setDevTreeEnv(const string& systemType) 803 { 804 // Init with default dtb 805 string newDeviceTree = "conf-aspeed-bmc-ibm-rainier-p1.dtb"; 806 static const deviceTreeMap deviceTreeSystemTypeMap = { 807 {RAINIER_2U, "conf-aspeed-bmc-ibm-rainier-p1.dtb"}, 808 {RAINIER_2U_V2, "conf-aspeed-bmc-ibm-rainier.dtb"}, 809 {RAINIER_4U, "conf-aspeed-bmc-ibm-rainier-4u-p1.dtb"}, 810 {RAINIER_4U_V2, "conf-aspeed-bmc-ibm-rainier-4u.dtb"}, 811 {RAINIER_1S4U, "conf-aspeed-bmc-ibm-rainier-1s4u.dtb"}, 812 {EVEREST, "conf-aspeed-bmc-ibm-everest.dtb"}, 813 {EVEREST_V2, "conf-aspeed-bmc-ibm-everest.dtb"}, 814 {BONNELL, "conf-aspeed-bmc-ibm-bonnell.dtb"}}; 815 816 if (deviceTreeSystemTypeMap.find(systemType) != 817 deviceTreeSystemTypeMap.end()) 818 { 819 newDeviceTree = deviceTreeSystemTypeMap.at(systemType); 820 } 821 else 822 { 823 // System type not supported 824 string err = "This System type not found/supported in dtb table " + 825 systemType + 826 ".Please check the HW and IM keywords in the system " 827 "VPD.Breaking..."; 828 829 // map to hold additional data in case of logging pel 830 PelAdditionalData additionalData{}; 831 additionalData.emplace("DESCRIPTION", err); 832 createPEL(additionalData, PelSeverity::WARNING, 833 errIntfForInvalidSystemType, nullptr); 834 exit(-1); 835 } 836 837 string readVarValue; 838 bool envVarFound = false; 839 840 vector<string> output = executeCmd("/sbin/fw_printenv"); 841 for (const auto& entry : output) 842 { 843 size_t pos = entry.find("="); 844 string key = entry.substr(0, pos); 845 if (key != "fitconfig") 846 { 847 continue; 848 } 849 850 envVarFound = true; 851 if (pos + 1 < entry.size()) 852 { 853 readVarValue = entry.substr(pos + 1); 854 if (readVarValue.find(newDeviceTree) != string::npos) 855 { 856 // fitconfig is Updated. No action needed 857 break; 858 } 859 } 860 // set env and reboot and break. 861 setEnvAndReboot(key, newDeviceTree); 862 exit(0); 863 } 864 865 // check If env var Not found 866 if (!envVarFound) 867 { 868 setEnvAndReboot("fitconfig", newDeviceTree); 869 } 870 } 871 872 /** 873 * @brief Parse the given EEPROM file. 874 * 875 * @param[in] vpdFilePath - Path of EEPROM file 876 * @param[in] js- Reference to vpd inventory json object 877 * @return Parsed VPD map 878 */ 879 std::variant<KeywordVpdMap, openpower::vpd::Store> 880 parseVpdFile(const std::string& vpdFilePath, const nlohmann::json& js) 881 { 882 uint32_t vpdStartOffset = 0; 883 for (const auto& item : js["frus"][vpdFilePath]) 884 { 885 if (item.find("offset") != item.end()) 886 { 887 vpdStartOffset = item["offset"]; 888 break; 889 } 890 } 891 892 Binary vpdVector = getVpdDataInVector(js, vpdFilePath); 893 894 ParserInterface* parser = ParserFactory::getParser( 895 vpdVector, 896 (pimPath + js["frus"][vpdFilePath][0]["inventoryPath"] 897 .get_ref<const nlohmann::json::string_t&>()), 898 vpdFilePath, vpdStartOffset); 899 900 auto parseResult = parser->parse(); 901 902 // release the parser object 903 ParserFactory::freeParser(parser); 904 905 return parseResult; 906 } 907 908 /* 909 * @brief This API retrieves the hardware backup in map 910 * 911 * @param[in] systemVpdBackupPath - The path that backs up the system VPD. 912 * @param[in] backupVpdInvPath - FRU inventory path. 913 * @param[in] js - JSON object. 914 * @param[out] backupVpdMap - An IPZ VPD map containing the parsed backup VPD. 915 * 916 * */ 917 void getBackupVpdInMap(const string& systemVpdBackupPath, 918 const string& backupVpdInvPath, const nlohmann::json& js, 919 Parsed& backupVpdMap) 920 { 921 PelAdditionalData additionalData{}; 922 923 if (!fs::exists(systemVpdBackupPath)) 924 { 925 string errorMsg = "Device path "; 926 errorMsg += systemVpdBackupPath; 927 errorMsg += " does not exist"; 928 929 additionalData.emplace("DESCRIPTION", errorMsg); 930 931 additionalData.emplace("CALLOUT_INVENTORY_PATH", 932 INVENTORY_PATH + backupVpdInvPath); 933 934 createPEL(additionalData, PelSeverity::ERROR, errIntfForStreamFail, 935 nullptr); 936 } 937 else 938 { 939 auto backupVpdParsedResult = parseVpdFile(systemVpdBackupPath, js); 940 941 if (auto pVal = get_if<Store>(&backupVpdParsedResult)) 942 { 943 backupVpdMap = pVal->getVpdMap(); 944 } 945 else 946 { 947 std::cerr << "Invalid format of VPD in back up. Restore aborted." 948 << std::endl; 949 } 950 } 951 } 952 953 void updateVpdDataOnHw(const std::string& vpdFilePath, nlohmann::json& js, 954 const std::string& recName, const std::string& kwName, 955 const Binary& kwdData) 956 { 957 const std::string& fruInvPath = 958 js["frus"][vpdFilePath][0]["inventoryPath"] 959 .get_ref<const nlohmann::json::string_t&>(); 960 961 EditorImpl edit(vpdFilePath, js, recName, kwName, fruInvPath); 962 963 uint32_t offset = 0; 964 // Setup offset, if any 965 for (const auto& item : js["frus"][vpdFilePath]) 966 { 967 if (item.find("offset") != item.end()) 968 { 969 offset = item["offset"]; 970 break; 971 } 972 } 973 974 // update keyword data on to EEPROM file 975 // Note: Updating keyword data on cache is 976 // handled via PIM Notify call hence passing 977 // the updCache flag value as false here. 978 edit.updateKeyword(kwdData, offset, false); 979 } 980 981 /** 982 * @brief API to check if we need to restore system VPD 983 * This functionality is only applicable for IPZ VPD data. 984 985 * @param[in] vpdMap - IPZ vpd map 986 * @param[in] objectPath - Object path for the FRU 987 * @param[in] js - JSON Object 988 * @param[in] isBackupOnCache - Denotes whether the backup is on cache/hardware 989 */ 990 void restoreSystemVPD(Parsed& vpdMap, const string& objectPath, 991 nlohmann::json& js, bool isBackupOnCache = true) 992 { 993 std::string systemVpdBackupPath{}; 994 std::string backupVpdInvPath{}; 995 Parsed backupVpdMap{}; 996 997 if (!isBackupOnCache) 998 { 999 // Get the value of systemvpdBackupPath field from json 1000 systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value( 1001 "systemVpdBackupPath", ""); 1002 1003 backupVpdInvPath = js["frus"][systemVpdBackupPath][0]["inventoryPath"] 1004 .get_ref<const nlohmann::json::string_t&>(); 1005 1006 getBackupVpdInMap(systemVpdBackupPath, backupVpdInvPath, js, 1007 backupVpdMap); 1008 1009 if (backupVpdMap.empty()) 1010 { 1011 std::cerr << "Backup VPD map is empty" << std::endl; 1012 return; 1013 } 1014 } 1015 1016 for (const auto& systemRecKwdPair : svpdKwdMap) 1017 { 1018 const string& recordName = systemRecKwdPair.first; 1019 auto it = vpdMap.find(recordName); 1020 1021 // check if record is found in map we got by parser 1022 if (it != vpdMap.end()) 1023 { 1024 const auto& kwdListForRecord = systemRecKwdPair.second; 1025 for (const auto& keywordInfo : kwdListForRecord) 1026 { 1027 const auto keywordName = get<0>(keywordInfo); 1028 1029 DbusPropertyMap& kwdValMap = it->second; 1030 auto iterator = kwdValMap.find(keywordName); 1031 1032 if (iterator != kwdValMap.end()) 1033 { 1034 string& kwdValue = iterator->second; 1035 1036 std::string backupValue{}; 1037 const auto& defaultValue = get<1>(keywordInfo); 1038 const auto& backupVpdRecName = get<4>(keywordInfo); 1039 const auto& backupVpdKwName = get<5>(keywordInfo); 1040 1041 // If the 'isBackupOnCache' flag is false, we need 1042 // to backup the systemVPD on the specified fru's eeprom 1043 // path or restore it from the specified fru's eeprom path. 1044 if (isBackupOnCache) 1045 { 1046 // check bus data 1047 backupValue = readBusProperty( 1048 objectPath, ipzVpdInf + recordName, keywordName); 1049 } 1050 else 1051 { 1052 backupValue = getKwVal(backupVpdMap, backupVpdRecName, 1053 backupVpdKwName); 1054 1055 if (backupValue.empty()) 1056 { 1057 string errorMsg{}; 1058 if (backupVpdMap.find(backupVpdRecName) == 1059 backupVpdMap.end()) 1060 { 1061 errorMsg = backupVpdRecName + 1062 " Record does not exist in " 1063 "the EEPROM file "; 1064 } 1065 else 1066 { 1067 errorMsg = backupVpdKwName + 1068 " Keyword not found or empty."; 1069 } 1070 1071 errorMsg += systemVpdBackupPath; 1072 1073 PelAdditionalData additionalData; 1074 additionalData.emplace("DESCRIPTION", errorMsg); 1075 1076 createPEL(additionalData, PelSeverity::ERROR, 1077 errIntfForInvalidVPD, nullptr); 1078 1079 continue; 1080 } 1081 } 1082 1083 Binary backupDataInBinary(backupValue.begin(), 1084 backupValue.end()); 1085 1086 Binary kwdDataInBinary(kwdValue.begin(), kwdValue.end()); 1087 1088 if (backupDataInBinary != defaultValue) 1089 { 1090 if (kwdDataInBinary != defaultValue) 1091 { 1092 // both the data are present, check for mismatch 1093 if (backupValue != kwdValue) 1094 { 1095 string errMsg = "Mismatch found between backup " 1096 "and primary VPD for record: "; 1097 errMsg += (*it).first; 1098 errMsg += " and keyword: "; 1099 errMsg += keywordName; 1100 1101 std::ostringstream busStream; 1102 for (uint16_t byte : backupValue) 1103 { 1104 busStream << std::setfill('0') 1105 << std::setw(2) << std::hex 1106 << "0x" << byte << " "; 1107 } 1108 1109 std::ostringstream vpdStream; 1110 for (uint16_t byte : kwdValue) 1111 { 1112 vpdStream << std::setfill('0') 1113 << std::setw(2) << std::hex 1114 << "0x" << byte << " "; 1115 } 1116 1117 // data mismatch 1118 PelAdditionalData additionalData; 1119 1120 additionalData.emplace("DESCRIPTION", errMsg); 1121 additionalData.emplace( 1122 "Value read from Backup: ", 1123 busStream.str()); 1124 additionalData.emplace( 1125 "Value read from Primary: ", 1126 vpdStream.str()); 1127 1128 createPEL(additionalData, PelSeverity::WARNING, 1129 errIntfForVPDMismatch, nullptr); 1130 1131 if (!isBackupOnCache) 1132 { 1133 // Backing up or restoring from a hardware 1134 // path does not requires copying the backup 1135 // data to the VPD map, as this will result 1136 // in a mismatch between the primary VPD and 1137 // its cache. 1138 continue; 1139 } 1140 } 1141 else 1142 { 1143 // both the backup and primary data is 1144 // non-default and same. Nothing needs to be 1145 // done. 1146 continue; 1147 } 1148 } 1149 1150 // If the backup is on the cache we need to copy the 1151 // backup data to the VPD map to ensure there is no 1152 // mimatch b/n them. So if backup data is not default, 1153 // then irrespective of primary data(default or other 1154 // than backup), copy the backup data to vpd map as we 1155 // don't need to change the backup data in either case 1156 // in the process of restoring system vpd. 1157 kwdValue = backupValue; 1158 1159 // If the backup data is on the base panel the restoring 1160 // of Backup VPD on to the system backplane VPD 1161 // file is done here not through the VPD manager code 1162 // path. This is to have the logic of restoring data on 1163 // to the cache & hardware in the same code path. 1164 if (!isBackupOnCache) 1165 { 1166 // copy backup VPD on to system backplane 1167 // EEPROM file. 1168 updateVpdDataOnHw(systemVpdFilePath, js, recordName, 1169 keywordName, backupDataInBinary); 1170 } 1171 } 1172 else if (kwdDataInBinary == defaultValue && 1173 get<2>(keywordInfo)) // Check isPELRequired is true 1174 { 1175 string errMsg = "Found default value on both backup " 1176 "and primary VPD for record: "; 1177 errMsg += (*it).first; 1178 errMsg += " and keyword: "; 1179 errMsg += keywordName; 1180 errMsg += ". Update primary VPD."; 1181 1182 // mfg default on both backup and primary, log PEL 1183 PelAdditionalData additionalData; 1184 additionalData.emplace("DESCRIPTION", errMsg); 1185 1186 createPEL(additionalData, PelSeverity::ERROR, 1187 errIntfForVPDDefault, nullptr); 1188 1189 continue; 1190 } 1191 else if ((kwdDataInBinary != defaultValue) && 1192 (!isBackupOnCache)) 1193 { 1194 // update primary VPD on to backup VPD file 1195 updateVpdDataOnHw(systemVpdBackupPath, js, 1196 backupVpdRecName, backupVpdKwName, 1197 kwdDataInBinary); 1198 1199 // copy primary VPD to backup VPD to publish on 1200 // DBus 1201 backupVpdMap.find(backupVpdRecName) 1202 ->second.find(backupVpdKwName) 1203 ->second = kwdValue; 1204 } 1205 } 1206 } 1207 } 1208 } 1209 } 1210 1211 /** 1212 * @brief This checks for is this FRU a processor 1213 * And if yes, then checks for is this primary 1214 * 1215 * @param[in] js- vpd json to get the information about this FRU 1216 * @param[in] filePath- FRU vpd 1217 * 1218 * @return true/false 1219 */ 1220 bool isThisPrimaryProcessor(nlohmann::json& js, const string& filePath) 1221 { 1222 bool isProcessor = false; 1223 bool isPrimary = false; 1224 1225 for (const auto& item : js["frus"][filePath]) 1226 { 1227 if (item.find("extraInterfaces") != item.end()) 1228 { 1229 for (const auto& eI : item["extraInterfaces"].items()) 1230 { 1231 if (eI.key().find("Inventory.Item.Cpu") != string::npos) 1232 { 1233 isProcessor = true; 1234 } 1235 } 1236 } 1237 1238 if (isProcessor) 1239 { 1240 string cpuType = item.value("cpuType", ""); 1241 if (cpuType == "primary") 1242 { 1243 isPrimary = true; 1244 } 1245 } 1246 } 1247 1248 return (isProcessor && isPrimary); 1249 } 1250 1251 /** 1252 * @brief This finds DIMM vpd in vpd json and enables them by binding the device 1253 * driver 1254 * @param[in] js- vpd json to iterate through and take action if it is DIMM 1255 */ 1256 void doEnableAllDimms(nlohmann::json& js) 1257 { 1258 // iterate over each fru 1259 for (const auto& eachFru : js["frus"].items()) 1260 { 1261 // skip the driver binding if eeprom already exists 1262 if (fs::exists(eachFru.key())) 1263 { 1264 continue; 1265 } 1266 1267 for (const auto& eachInventory : eachFru.value()) 1268 { 1269 if (eachInventory.find("extraInterfaces") != eachInventory.end()) 1270 { 1271 for (const auto& eI : eachInventory["extraInterfaces"].items()) 1272 { 1273 if (eI.key().find("Inventory.Item.Dimm") != string::npos) 1274 { 1275 string dimmVpd = eachFru.key(); 1276 // fetch it from 1277 // "/sys/bus/i2c/drivers/at24/414-0050/eeprom" 1278 1279 regex matchPatern("([0-9]+-[0-9]{4})"); 1280 smatch matchFound; 1281 if (regex_search(dimmVpd, matchFound, matchPatern)) 1282 { 1283 vector<string> i2cReg; 1284 boost::split(i2cReg, matchFound.str(0), 1285 boost::is_any_of("-")); 1286 1287 // remove 0s from begining 1288 const regex pattern("^0+(?!$)"); 1289 for (auto& i : i2cReg) 1290 { 1291 i = regex_replace(i, pattern, ""); 1292 } 1293 1294 // For ISDIMM which uses ee1004 driver 1295 // the below is done 1296 size_t stringFound = dimmVpd.find("ee1004"); 1297 if (stringFound != string::npos) 1298 { 1299 // echo ee1004 0x50 > 1300 // /sys/bus/i2c/devices/i2c-110/new_device 1301 string cmnd = "echo ee1004 0x" + i2cReg[1] + 1302 " > /sys/bus/i2c/devices/i2c-" + 1303 i2cReg[0] + "/new_device"; 1304 executeCmd(cmnd); 1305 } 1306 else if (i2cReg.size() == 2) 1307 { 1308 // echo 24c32 0x50 > 1309 // /sys/bus/i2c/devices/i2c-16/new_device 1310 string cmnd = "echo 24c32 0x" + i2cReg[1] + 1311 " > /sys/bus/i2c/devices/i2c-" + 1312 i2cReg[0] + "/new_device"; 1313 executeCmd(cmnd); 1314 } 1315 } 1316 } 1317 } 1318 } 1319 } 1320 } 1321 } 1322 1323 /** 1324 * @brief Check if the given CPU is an IO only chip. 1325 * The CPU is termed as IO, whose all of the cores are bad and can never be 1326 * used. Those CPU chips can be used for IO purpose like connecting PCIe devices 1327 * etc., The CPU whose every cores are bad, can be identified from the CP00 1328 * record's PG keyword, only if all of the 8 EQs' value equals 0xE7F9FF. (1EQ 1329 * has 4 cores grouped together by sharing its cache memory.) 1330 * @param [in] pgKeyword - PG Keyword of CPU. 1331 * @return true if the given cpu is an IO, false otherwise. 1332 */ 1333 static bool isCPUIOGoodOnly(const string& pgKeyword) 1334 { 1335 const unsigned char io[] = {0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 1336 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 1337 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF}; 1338 // EQ0 index (in PG keyword) starts at 97 (with offset starting from 0). 1339 // Each EQ carries 3 bytes of data. Totally there are 8 EQs. If all EQs' 1340 // value equals 0xE7F9FF, then the cpu has no good cores and its treated as 1341 // IO. 1342 if (memcmp(io, pgKeyword.data() + 97, 24) == 0) 1343 { 1344 return true; 1345 } 1346 1347 // The CPU is not an IO 1348 return false; 1349 } 1350 1351 /** 1352 * @brief Function to bring MUX out of idle state 1353 * 1354 * This finds All the MUX defined in the system json and enables 1355 * them by setting the holdidle parameter to 0. 1356 * @param[in] js- system json to iterate through and take action 1357 */ 1358 void doEnableAllMuxChips(const nlohmann::json& js) 1359 { 1360 // Do we have the mandatory "muxes" section? 1361 if (js.find("muxes") != js.end()) 1362 { 1363 std::cout << "Enabling all the MUX on the system " << std::endl; 1364 // iterate over each MUX detail and enable them 1365 for (const auto& item : js["muxes"]) 1366 { 1367 if (item.find("holdidlepath") != item.end()) 1368 { 1369 const std::string& holdidle = item["holdidlepath"]; 1370 std::cout << "Setting holdidle state for " << holdidle 1371 << "to 0 " << std::endl; 1372 string cmd = "echo 0 > " + holdidle; 1373 executeCmd(cmd); 1374 } 1375 } 1376 std::cout << "Completed enabling all the MUX on the system " 1377 << std::endl; 1378 } 1379 else 1380 { 1381 std::cout << "No MUX was defined for the system" << std::endl; 1382 } 1383 } 1384 1385 /** 1386 * @brief Populate Dbus. 1387 * This method invokes all the populateInterface functions 1388 * and notifies PIM about dbus object. 1389 * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the 1390 * input. 1391 * @param[in] js - Inventory json object 1392 * @param[in] filePath - Path of the vpd file 1393 * @param[in] preIntrStr - Interface string 1394 */ 1395 template <typename T> 1396 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath) 1397 { 1398 inventory::InterfaceMap interfaces; 1399 inventory::ObjectMap objects; 1400 inventory::PropertyMap prop; 1401 string ccinFromVpd; 1402 1403 bool isSystemVpd = (filePath == systemVpdFilePath); 1404 if constexpr (is_same<T, Parsed>::value) 1405 { 1406 ccinFromVpd = getKwVal(vpdMap, "VINI", "CC"); 1407 transform(ccinFromVpd.begin(), ccinFromVpd.end(), ccinFromVpd.begin(), 1408 ::toupper); 1409 1410 if (isSystemVpd) 1411 { 1412 string mboardPath = 1413 js["frus"][filePath].at(0).value("inventoryPath", ""); 1414 1415 // Get the value of systemvpdBackupPath field from json 1416 const std::string& systemVpdBackupPath = 1417 js["frus"][filePath].at(0).value("systemVpdBackupPath", ""); 1418 1419 if (systemVpdBackupPath.empty()) 1420 { 1421 std::vector<std::string> interfaces = {motherBoardInterface}; 1422 // call mapper to check for object path creation 1423 MapperResponse subTree = 1424 getObjectSubtreeForInterfaces(pimPath, 0, interfaces); 1425 1426 // Attempt system VPD restore if we have a motherboard 1427 // object in the inventory. 1428 if ((subTree.size() != 0) && 1429 (subTree.find(pimPath + mboardPath) != subTree.end())) 1430 { 1431 restoreSystemVPD(vpdMap, mboardPath, js); 1432 } 1433 else 1434 { 1435 log<level::ERR>("No object path found"); 1436 } 1437 } 1438 else 1439 { 1440 restoreSystemVPD(vpdMap, mboardPath, js, false); 1441 } 1442 } 1443 else 1444 { 1445 // check if it is processor vpd. 1446 auto isPrimaryCpu = isThisPrimaryProcessor(js, filePath); 1447 1448 if (isPrimaryCpu) 1449 { 1450 auto ddVersion = getKwVal(vpdMap, "CRP0", "DD"); 1451 1452 auto chipVersion = atoi(ddVersion.substr(1, 2).c_str()); 1453 1454 if (chipVersion >= 2) 1455 { 1456 doEnableAllDimms(js); 1457 // Sleep for a few seconds to let the DIMM parses start 1458 using namespace std::chrono_literals; 1459 std::this_thread::sleep_for(5s); 1460 } 1461 } 1462 } 1463 } 1464 1465 auto processFactoryReset = false; 1466 1467 if (isSystemVpd) 1468 { 1469 string systemJsonName{}; 1470 if constexpr (is_same<T, Parsed>::value) 1471 { 1472 // pick the right system json 1473 systemJsonName = getSystemsJson(vpdMap); 1474 } 1475 1476 fs::path target = systemJsonName; 1477 fs::path link = INVENTORY_JSON_SYM_LINK; 1478 1479 // If the symlink does not exist, we treat that as a factory reset 1480 processFactoryReset = !fs::exists(INVENTORY_JSON_SYM_LINK); 1481 1482 // Create the directory for hosting the symlink 1483 fs::create_directories(VPD_FILES_PATH); 1484 // unlink the symlink previously created (if any) 1485 remove(INVENTORY_JSON_SYM_LINK); 1486 // create a new symlink based on the system 1487 fs::create_symlink(target, link); 1488 1489 // Reloading the json 1490 ifstream inventoryJson(link); 1491 js = json::parse(inventoryJson); 1492 inventoryJson.close(); 1493 } 1494 1495 for (const auto& item : js["frus"][filePath]) 1496 { 1497 const auto& objectPath = item["inventoryPath"]; 1498 sdbusplus::message::object_path object(objectPath); 1499 1500 vector<string> ccinList; 1501 if (item.find("ccin") != item.end()) 1502 { 1503 for (const auto& cc : item["ccin"]) 1504 { 1505 string ccin = cc; 1506 transform(ccin.begin(), ccin.end(), ccin.begin(), ::toupper); 1507 ccinList.push_back(ccin); 1508 } 1509 } 1510 1511 if (!ccinFromVpd.empty() && !ccinList.empty() && 1512 (find(ccinList.begin(), ccinList.end(), ccinFromVpd) == 1513 ccinList.end())) 1514 { 1515 continue; 1516 } 1517 1518 if ((isSystemVpd) || (item.value("noprime", false))) 1519 { 1520 // Populate one time properties for the system VPD and its sub-frus 1521 // and for other non-primeable frus. 1522 // For the remaining FRUs, this will get handled as a part of 1523 // priming the inventory. 1524 setOneTimeProperties(objectPath, interfaces); 1525 } 1526 1527 // Populate the VPD keywords and the common interfaces only if we 1528 // are asked to inherit that data from the VPD, else only add the 1529 // extraInterfaces. 1530 if (item.value("inherit", true)) 1531 { 1532 if constexpr (is_same<T, Parsed>::value) 1533 { 1534 // Each record in the VPD becomes an interface and all 1535 // keyword within the record are properties under that 1536 // interface. 1537 for (const auto& record : vpdMap) 1538 { 1539 populateFruSpecificInterfaces( 1540 record.second, ipzVpdInf + record.first, interfaces); 1541 } 1542 } 1543 else if constexpr (is_same<T, KeywordVpdMap>::value) 1544 { 1545 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces); 1546 } 1547 if (js.find("commonInterfaces") != js.end()) 1548 { 1549 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, 1550 isSystemVpd); 1551 } 1552 } 1553 else 1554 { 1555 // Check if we have been asked to inherit specific record(s) 1556 if constexpr (is_same<T, Parsed>::value) 1557 { 1558 if (item.find("copyRecords") != item.end()) 1559 { 1560 for (const auto& record : item["copyRecords"]) 1561 { 1562 const string& recordName = record; 1563 if (vpdMap.find(recordName) != vpdMap.end()) 1564 { 1565 populateFruSpecificInterfaces( 1566 vpdMap.at(recordName), ipzVpdInf + recordName, 1567 interfaces); 1568 } 1569 } 1570 } 1571 } 1572 } 1573 // Populate interfaces and properties that are common to every FRU 1574 // and additional interface that might be defined on a per-FRU 1575 // basis. 1576 if (item.find("extraInterfaces") != item.end()) 1577 { 1578 populateInterfaces(item["extraInterfaces"], interfaces, vpdMap, 1579 isSystemVpd); 1580 if constexpr (is_same<T, Parsed>::value) 1581 { 1582 if (item["extraInterfaces"].find( 1583 "xyz.openbmc_project.Inventory.Item.Cpu") != 1584 item["extraInterfaces"].end()) 1585 { 1586 if (isCPUIOGoodOnly(getKwVal(vpdMap, "CP00", "PG"))) 1587 { 1588 interfaces[invItemIntf]["PrettyName"] = "IO Module"; 1589 } 1590 } 1591 } 1592 } 1593 1594 // embedded property(true or false) says whether the subfru is embedded 1595 // into the parent fru (or) not. VPD sets Present property only for 1596 // embedded frus. If the subfru is not an embedded FRU, the subfru may 1597 // or may not be physically present. Those non embedded frus will always 1598 // have Present=false irrespective of its physical presence or absence. 1599 // Eg: nvme drive in nvme slot is not an embedded FRU. So don't set 1600 // Present to true for such sub frus. 1601 // Eg: ethernet port is embedded into bmc card. So set Present to true 1602 // for such sub frus. Also donot populate present property for embedded 1603 // subfru which is synthesized. Currently there is no subfru which are 1604 // both embedded and synthesized. But still the case is handled here. 1605 if ((item.value("embedded", true)) && 1606 (!item.value("synthesized", false))) 1607 { 1608 // Check if its required to handle presence for this FRU. 1609 if (item.value("handlePresence", true)) 1610 { 1611 inventory::PropertyMap presProp; 1612 presProp.emplace("Present", true); 1613 insertOrMerge(interfaces, invItemIntf, move(presProp)); 1614 } 1615 } 1616 1617 if constexpr (is_same<T, Parsed>::value) 1618 { 1619 // Restore asset tag, if needed 1620 if (processFactoryReset && objectPath == "/system") 1621 { 1622 fillAssetTag(interfaces, vpdMap); 1623 } 1624 } 1625 1626 objects.emplace(move(object), move(interfaces)); 1627 } 1628 1629 if (isSystemVpd) 1630 { 1631 inventory::ObjectMap primeObject = primeInventory(js, vpdMap); 1632 objects.insert(primeObject.begin(), primeObject.end()); 1633 1634 // set the U-boot environment variable for device-tree 1635 if constexpr (is_same<T, Parsed>::value) 1636 { 1637 setDevTreeEnv(fs::path(getSystemsJson(vpdMap)).filename()); 1638 } 1639 } 1640 1641 // Notify PIM 1642 common::utility::callPIM(move(objects)); 1643 } 1644 1645 int main(int argc, char** argv) 1646 { 1647 int rc = 0; 1648 json js{}; 1649 Binary vpdVector{}; 1650 string file{}; 1651 string driver{}; 1652 // map to hold additional data in case of logging pel 1653 PelAdditionalData additionalData{}; 1654 1655 // this is needed to hold base fru inventory path in case there is ECC or 1656 // vpd exception while parsing the file 1657 std::string baseFruInventoryPath = {}; 1658 1659 // It holds the backup EEPROM file path for the system backplane's critical 1660 // data 1661 std::string systemVpdBackupPath{}; 1662 1663 // It holds the inventory path of backup EEPROM file 1664 std::string backupVpdInvPath{}; 1665 1666 bool isSystemVpd = false; 1667 1668 // severity for PEL 1669 PelSeverity pelSeverity = PelSeverity::WARNING; 1670 1671 try 1672 { 1673 App app{"ibm-read-vpd - App to read IPZ/Jedec format VPD, parse it and " 1674 "store it in DBUS"}; 1675 1676 app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") 1677 ->required(); 1678 1679 app.add_option("--driver", driver, 1680 "Driver used by kernel (at24,at25,ee1004)") 1681 ->required(); 1682 1683 CLI11_PARSE(app, argc, argv); 1684 1685 // PEL severity should be ERROR in case of any system VPD failure 1686 if (file == systemVpdFilePath) 1687 { 1688 pelSeverity = PelSeverity::ERROR; 1689 isSystemVpd = true; 1690 } 1691 1692 // Check if input file is not empty. 1693 if ((file.empty()) || (driver.empty())) 1694 { 1695 std::cerr << "Encountered empty input parameter file [" << file 1696 << "] driver [" << driver << "]" << std::endl; 1697 return 0; 1698 } 1699 1700 // Check if currently supported driver or not 1701 if ((driver != at24driver) && (driver != at25driver) && 1702 (driver != ee1004driver)) 1703 { 1704 std::cerr << "The driver [" << driver << "] is not supported." 1705 << std::endl; 1706 return 0; 1707 } 1708 1709 auto jsonToParse = INVENTORY_JSON_DEFAULT; 1710 1711 // If the symlink exists, it means it has been setup for us, switch the 1712 // path 1713 if (fs::exists(INVENTORY_JSON_SYM_LINK)) 1714 { 1715 jsonToParse = INVENTORY_JSON_SYM_LINK; 1716 } 1717 1718 // Make sure that the file path we get is for a supported EEPROM 1719 ifstream inventoryJson(jsonToParse); 1720 if (!inventoryJson) 1721 { 1722 throw(VpdJsonException("Failed to access Json path", jsonToParse)); 1723 } 1724 1725 try 1726 { 1727 js = json::parse(inventoryJson); 1728 } 1729 catch (const json::parse_error& ex) 1730 { 1731 throw(VpdJsonException("Json parsing failed", jsonToParse)); 1732 } 1733 1734 // Do we have the mandatory "frus" section? 1735 if (js.find("frus") == js.end()) 1736 { 1737 throw(VpdJsonException("FRUs section not found in JSON", 1738 jsonToParse)); 1739 } 1740 1741 // Check if it's a udev path - patterned as(/ahb/ahb:apb/ahb:apb:bus@) 1742 if (file.find("/ahb:apb") != string::npos) 1743 { 1744 // Translate udev path to a generic /sys/bus/.. file path. 1745 udevToGenericPath(file, driver); 1746 1747 if ((js["frus"].find(file) != js["frus"].end()) && 1748 (file == systemVpdFilePath)) 1749 { 1750 std::cout << "We have already collected system VPD, skiping." 1751 << std::endl; 1752 return 0; 1753 } 1754 } 1755 1756 // Enable all mux which are used for connecting to the i2c on the pcie 1757 // slots for pcie cards. These are not enabled by kernel due to an issue 1758 // seen with Castello cards, where the i2c line hangs on a probe. 1759 // To run it only once have kept it under System vpd check. 1760 // we need to run this on all BMC reboots so kept here 1761 if (file == systemVpdFilePath) 1762 { 1763 doEnableAllMuxChips(js); 1764 } 1765 1766 if (file.empty()) 1767 { 1768 std::cerr << "The EEPROM path <" << file << "> is not valid."; 1769 return 0; 1770 } 1771 if (js["frus"].find(file) == js["frus"].end()) 1772 { 1773 std::cerr << "The EEPROM path [" << file 1774 << "] is not found in the json." << std::endl; 1775 return 0; 1776 } 1777 1778 if (!fs::exists(file)) 1779 { 1780 std::cout << "Device path: " << file 1781 << " does not exist. Spurious udev event? Exiting." 1782 << std::endl; 1783 return 0; 1784 } 1785 1786 // In case of system VPD it will already be filled, Don't have to 1787 // overwrite that. 1788 if (baseFruInventoryPath.empty()) 1789 { 1790 baseFruInventoryPath = js["frus"][file][0]["inventoryPath"]; 1791 } 1792 1793 // Check if we can read the VPD file based on the power state 1794 // We skip reading VPD when the power is ON in two scenarios: 1795 // 1) The eeprom we are trying to read is that of the system VPD and the 1796 // JSON symlink is already setup (the symlink's existence tells us we 1797 // are not coming out of a factory reset) 1798 // 2) The JSON tells us that the FRU EEPROM cannot be 1799 // read when we are powered ON. 1800 if (js["frus"][file].at(0).value("powerOffOnly", false) || 1801 (file == systemVpdFilePath && fs::exists(INVENTORY_JSON_SYM_LINK))) 1802 { 1803 if ("xyz.openbmc_project.State.Chassis.PowerState.On" == 1804 getPowerState()) 1805 { 1806 std::cout << "This VPD cannot be read when power is ON" 1807 << std::endl; 1808 return 0; 1809 } 1810 } 1811 1812 // Check if this VPD should be recollected at all 1813 if (!needsRecollection(js, file)) 1814 { 1815 std::cout << "Skip VPD recollection for: " << file << std::endl; 1816 return 0; 1817 } 1818 1819 try 1820 { 1821 variant<KeywordVpdMap, Store> parseResult; 1822 parseResult = parseVpdFile(file, js); 1823 1824 if (isSystemVpd) 1825 { 1826 // Get the value of systemVpdBackupPath field from json 1827 systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value( 1828 "systemVpdBackupPath", ""); 1829 1830 if (!systemVpdBackupPath.empty()) 1831 { 1832 backupVpdInvPath = 1833 js["frus"][systemVpdBackupPath][0]["inventoryPath"] 1834 .get_ref<const nlohmann::json::string_t&>(); 1835 } 1836 } 1837 1838 if (auto pVal = get_if<Store>(&parseResult)) 1839 { 1840 populateDbus(pVal->getVpdMap(), js, file); 1841 } 1842 else if (auto pVal = get_if<KeywordVpdMap>(&parseResult)) 1843 { 1844 populateDbus(*pVal, js, file); 1845 } 1846 } 1847 catch (const exception& e) 1848 { 1849 if (!systemVpdBackupPath.empty()) 1850 { 1851 file = systemVpdBackupPath; 1852 baseFruInventoryPath = backupVpdInvPath; 1853 } 1854 1855 executePostFailAction(js, file); 1856 throw; 1857 } 1858 } 1859 catch (const VpdJsonException& ex) 1860 { 1861 additionalData.emplace("JSON_PATH", ex.getJsonPath()); 1862 additionalData.emplace("DESCRIPTION", ex.what()); 1863 createPEL(additionalData, pelSeverity, errIntfForJsonFailure, nullptr); 1864 1865 std::cerr << ex.what() << "\n"; 1866 rc = -1; 1867 } 1868 catch (const VpdEccException& ex) 1869 { 1870 additionalData.emplace("DESCRIPTION", "ECC check failed"); 1871 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1872 INVENTORY_PATH + baseFruInventoryPath); 1873 createPEL(additionalData, pelSeverity, errIntfForEccCheckFail, nullptr); 1874 1875 if (systemVpdBackupPath.empty()) 1876 { 1877 dumpBadVpd(file, vpdVector); 1878 } 1879 1880 std::cerr << ex.what() << "\n"; 1881 rc = -1; 1882 } 1883 catch (const VpdDataException& ex) 1884 { 1885 if (isThisPcieOnPass1planar(js, file)) 1886 { 1887 std::cout << "Pcie_device [" << file 1888 << "]'s VPD is not valid on PASS1 planar.Ignoring.\n"; 1889 rc = 0; 1890 } 1891 else if (!(isPresent(js, file).value_or(true))) 1892 { 1893 std::cout << "FRU at: " << file 1894 << " is not detected present. Ignore parser error.\n"; 1895 rc = 0; 1896 } 1897 else 1898 { 1899 string errorMsg = 1900 "VPD file is either empty or invalid. Parser failed for ["; 1901 errorMsg += file; 1902 errorMsg += "], with error = " + std::string(ex.what()); 1903 1904 additionalData.emplace("DESCRIPTION", errorMsg); 1905 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1906 INVENTORY_PATH + baseFruInventoryPath); 1907 createPEL(additionalData, pelSeverity, errIntfForInvalidVPD, 1908 nullptr); 1909 1910 rc = -1; 1911 } 1912 } 1913 catch (const exception& e) 1914 { 1915 dumpBadVpd(file, vpdVector); 1916 std::cerr << e.what() << "\n"; 1917 rc = -1; 1918 } 1919 1920 return rc; 1921 } 1922