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().find("Inventory.Item.") != 747 string::npos) 748 { 749 interfaces.emplace(move(eI.key()), move(props)); 750 } 751 else if (eI.key() == 752 "xyz.openbmc_project.Inventory.Item") 753 { 754 for (auto& val : eI.value().items()) 755 { 756 if (val.key() == "PrettyName") 757 { 758 presProp.emplace(val.key(), 759 val.value().get<string>()); 760 } 761 } 762 // Use insert_or_assign here as we may already have 763 // inserted the present property only earlier in 764 // this function under this same interface. 765 interfaces.insert_or_assign(eI.key(), 766 move(presProp)); 767 } 768 } 769 } 770 objects.emplace(move(object), move(interfaces)); 771 } 772 } 773 } 774 return objects; 775 } 776 777 /** 778 * @brief This API executes command to set environment variable 779 * And then reboot the system 780 * @param[in] key -env key to set new value 781 * @param[in] value -value to set. 782 */ 783 void setEnvAndReboot(const string& key, const string& value) 784 { 785 // set env and reboot and break. 786 executeCmd("/sbin/fw_setenv", key, value); 787 log<level::INFO>("Rebooting BMC to pick up new device tree"); 788 // make dbus call to reboot 789 auto bus = sdbusplus::bus::new_default_system(); 790 auto method = bus.new_method_call( 791 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 792 "org.freedesktop.systemd1.Manager", "Reboot"); 793 bus.call_noreply(method); 794 } 795 796 /* 797 * @brief This API checks for env var fitconfig. 798 * If not initialised OR updated as per the current system type, 799 * update this env var and reboot the system. 800 * 801 * @param[in] systemType IM kwd in vpd tells about which system type it is. 802 * */ 803 void setDevTreeEnv(const string& systemType) 804 { 805 // Init with default dtb 806 string newDeviceTree = "conf-aspeed-bmc-ibm-rainier-p1.dtb"; 807 static const deviceTreeMap deviceTreeSystemTypeMap = { 808 {RAINIER_2U, "conf-aspeed-bmc-ibm-rainier-p1.dtb"}, 809 {RAINIER_2U_V2, "conf-aspeed-bmc-ibm-rainier.dtb"}, 810 {RAINIER_4U, "conf-aspeed-bmc-ibm-rainier-4u-p1.dtb"}, 811 {RAINIER_4U_V2, "conf-aspeed-bmc-ibm-rainier-4u.dtb"}, 812 {RAINIER_1S4U, "conf-aspeed-bmc-ibm-rainier-1s4u.dtb"}, 813 {EVEREST, "conf-aspeed-bmc-ibm-everest.dtb"}, 814 {EVEREST_V2, "conf-aspeed-bmc-ibm-everest.dtb"}, 815 {BONNELL, "conf-aspeed-bmc-ibm-bonnell.dtb"}}; 816 817 if (deviceTreeSystemTypeMap.find(systemType) != 818 deviceTreeSystemTypeMap.end()) 819 { 820 newDeviceTree = deviceTreeSystemTypeMap.at(systemType); 821 } 822 else 823 { 824 // System type not supported 825 string err = "This System type not found/supported in dtb table " + 826 systemType + 827 ".Please check the HW and IM keywords in the system " 828 "VPD.Breaking..."; 829 830 // map to hold additional data in case of logging pel 831 PelAdditionalData additionalData{}; 832 additionalData.emplace("DESCRIPTION", err); 833 createPEL(additionalData, PelSeverity::WARNING, 834 errIntfForInvalidSystemType, nullptr); 835 exit(-1); 836 } 837 838 string readVarValue; 839 bool envVarFound = false; 840 841 vector<string> output = executeCmd("/sbin/fw_printenv"); 842 for (const auto& entry : output) 843 { 844 size_t pos = entry.find("="); 845 string key = entry.substr(0, pos); 846 if (key != "fitconfig") 847 { 848 continue; 849 } 850 851 envVarFound = true; 852 if (pos + 1 < entry.size()) 853 { 854 readVarValue = entry.substr(pos + 1); 855 if (readVarValue.find(newDeviceTree) != string::npos) 856 { 857 // fitconfig is Updated. No action needed 858 break; 859 } 860 } 861 // set env and reboot and break. 862 setEnvAndReboot(key, newDeviceTree); 863 exit(0); 864 } 865 866 // check If env var Not found 867 if (!envVarFound) 868 { 869 setEnvAndReboot("fitconfig", newDeviceTree); 870 } 871 } 872 873 /** 874 * @brief Parse the given EEPROM file. 875 * 876 * @param[in] vpdFilePath - Path of EEPROM file 877 * @param[in] js- Reference to vpd inventory json object 878 * @return Parsed VPD map 879 */ 880 std::variant<KeywordVpdMap, openpower::vpd::Store> 881 parseVpdFile(const std::string& vpdFilePath, const nlohmann::json& js) 882 { 883 uint32_t vpdStartOffset = 0; 884 for (const auto& item : js["frus"][vpdFilePath]) 885 { 886 if (item.find("offset") != item.end()) 887 { 888 vpdStartOffset = item["offset"]; 889 break; 890 } 891 } 892 893 Binary vpdVector = getVpdDataInVector(js, vpdFilePath); 894 895 ParserInterface* parser = ParserFactory::getParser( 896 vpdVector, 897 (pimPath + js["frus"][vpdFilePath][0]["inventoryPath"] 898 .get_ref<const nlohmann::json::string_t&>()), 899 vpdFilePath, vpdStartOffset); 900 901 auto parseResult = parser->parse(); 902 903 // release the parser object 904 ParserFactory::freeParser(parser); 905 906 return parseResult; 907 } 908 909 /* 910 * @brief This API retrieves the hardware backup in map 911 * 912 * @param[in] systemVpdBackupPath - The path that backs up the system VPD. 913 * @param[in] backupVpdInvPath - FRU inventory path. 914 * @param[in] js - JSON object. 915 * @param[out] backupVpdMap - An IPZ VPD map containing the parsed backup VPD. 916 * 917 * */ 918 void getBackupVpdInMap(const string& systemVpdBackupPath, 919 const string& backupVpdInvPath, const nlohmann::json& js, 920 Parsed& backupVpdMap) 921 { 922 PelAdditionalData additionalData{}; 923 924 if (!fs::exists(systemVpdBackupPath)) 925 { 926 string errorMsg = "Device path "; 927 errorMsg += systemVpdBackupPath; 928 errorMsg += " does not exist"; 929 930 additionalData.emplace("DESCRIPTION", errorMsg); 931 932 additionalData.emplace("CALLOUT_INVENTORY_PATH", 933 INVENTORY_PATH + backupVpdInvPath); 934 935 createPEL(additionalData, PelSeverity::ERROR, errIntfForStreamFail, 936 nullptr); 937 } 938 else 939 { 940 auto backupVpdParsedResult = parseVpdFile(systemVpdBackupPath, js); 941 942 if (auto pVal = get_if<Store>(&backupVpdParsedResult)) 943 { 944 backupVpdMap = pVal->getVpdMap(); 945 } 946 else 947 { 948 std::cerr << "Invalid format of VPD in back up. Restore aborted." 949 << std::endl; 950 } 951 } 952 } 953 954 void updateVpdDataOnHw(const std::string& vpdFilePath, nlohmann::json& js, 955 const std::string& recName, const std::string& kwName, 956 const Binary& kwdData) 957 { 958 const std::string& fruInvPath = 959 js["frus"][vpdFilePath][0]["inventoryPath"] 960 .get_ref<const nlohmann::json::string_t&>(); 961 962 EditorImpl edit(vpdFilePath, js, recName, kwName, fruInvPath); 963 964 uint32_t offset = 0; 965 // Setup offset, if any 966 for (const auto& item : js["frus"][vpdFilePath]) 967 { 968 if (item.find("offset") != item.end()) 969 { 970 offset = item["offset"]; 971 break; 972 } 973 } 974 975 // update keyword data on to EEPROM file 976 // Note: Updating keyword data on cache is 977 // handled via PIM Notify call hence passing 978 // the updCache flag value as false here. 979 edit.updateKeyword(kwdData, offset, false); 980 } 981 982 /** 983 * @brief API to check if we need to restore system VPD 984 * This functionality is only applicable for IPZ VPD data. 985 986 * @param[in] vpdMap - IPZ vpd map 987 * @param[in] objectPath - Object path for the FRU 988 * @param[in] js - JSON Object 989 * @param[in] isBackupOnCache - Denotes whether the backup is on cache/hardware 990 */ 991 void restoreSystemVPD(Parsed& vpdMap, const string& objectPath, 992 nlohmann::json& js, bool isBackupOnCache = true) 993 { 994 std::string systemVpdBackupPath{}; 995 std::string backupVpdInvPath{}; 996 Parsed backupVpdMap{}; 997 998 if (!isBackupOnCache) 999 { 1000 // Get the value of systemvpdBackupPath field from json 1001 systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value( 1002 "systemVpdBackupPath", ""); 1003 1004 backupVpdInvPath = js["frus"][systemVpdBackupPath][0]["inventoryPath"] 1005 .get_ref<const nlohmann::json::string_t&>(); 1006 1007 getBackupVpdInMap(systemVpdBackupPath, backupVpdInvPath, js, 1008 backupVpdMap); 1009 1010 if (backupVpdMap.empty()) 1011 { 1012 std::cerr << "Backup VPD map is empty" << std::endl; 1013 return; 1014 } 1015 } 1016 1017 for (const auto& systemRecKwdPair : svpdKwdMap) 1018 { 1019 const string& recordName = systemRecKwdPair.first; 1020 auto it = vpdMap.find(recordName); 1021 1022 // check if record is found in map we got by parser 1023 if (it != vpdMap.end()) 1024 { 1025 const auto& kwdListForRecord = systemRecKwdPair.second; 1026 for (const auto& keywordInfo : kwdListForRecord) 1027 { 1028 const auto keywordName = get<0>(keywordInfo); 1029 1030 DbusPropertyMap& kwdValMap = it->second; 1031 auto iterator = kwdValMap.find(keywordName); 1032 1033 if (iterator != kwdValMap.end()) 1034 { 1035 string& kwdValue = iterator->second; 1036 1037 std::string backupValue{}; 1038 const auto& defaultValue = get<1>(keywordInfo); 1039 const auto& backupVpdRecName = get<4>(keywordInfo); 1040 const auto& backupVpdKwName = get<5>(keywordInfo); 1041 1042 // If the 'isBackupOnCache' flag is false, we need 1043 // to backup the systemVPD on the specified fru's eeprom 1044 // path or restore it from the specified fru's eeprom path. 1045 if (isBackupOnCache) 1046 { 1047 // check bus data 1048 backupValue = readBusProperty( 1049 objectPath, ipzVpdInf + recordName, keywordName); 1050 } 1051 else 1052 { 1053 backupValue = getKwVal(backupVpdMap, backupVpdRecName, 1054 backupVpdKwName); 1055 1056 if (backupValue.empty()) 1057 { 1058 string errorMsg{}; 1059 if (backupVpdMap.find(backupVpdRecName) == 1060 backupVpdMap.end()) 1061 { 1062 errorMsg = backupVpdRecName + 1063 " Record does not exist in " 1064 "the EEPROM file "; 1065 } 1066 else 1067 { 1068 errorMsg = backupVpdKwName + 1069 " Keyword not found or empty."; 1070 } 1071 1072 errorMsg += systemVpdBackupPath; 1073 1074 PelAdditionalData additionalData; 1075 additionalData.emplace("DESCRIPTION", errorMsg); 1076 1077 createPEL(additionalData, PelSeverity::ERROR, 1078 errIntfForInvalidVPD, nullptr); 1079 1080 continue; 1081 } 1082 } 1083 1084 Binary backupDataInBinary(backupValue.begin(), 1085 backupValue.end()); 1086 1087 Binary kwdDataInBinary(kwdValue.begin(), kwdValue.end()); 1088 1089 if (backupDataInBinary != defaultValue) 1090 { 1091 if (kwdDataInBinary != defaultValue) 1092 { 1093 // both the data are present, check for mismatch 1094 if (backupValue != kwdValue) 1095 { 1096 string errMsg = "Mismatch found between backup " 1097 "and primary VPD for record: "; 1098 errMsg += (*it).first; 1099 errMsg += " and keyword: "; 1100 errMsg += keywordName; 1101 1102 std::ostringstream busStream; 1103 for (uint16_t byte : backupValue) 1104 { 1105 busStream << std::setfill('0') 1106 << std::setw(2) << std::hex 1107 << "0x" << byte << " "; 1108 } 1109 1110 std::ostringstream vpdStream; 1111 for (uint16_t byte : kwdValue) 1112 { 1113 vpdStream << std::setfill('0') 1114 << std::setw(2) << std::hex 1115 << "0x" << byte << " "; 1116 } 1117 1118 // data mismatch 1119 PelAdditionalData additionalData; 1120 1121 additionalData.emplace("DESCRIPTION", errMsg); 1122 additionalData.emplace( 1123 "Value read from Backup: ", 1124 busStream.str()); 1125 additionalData.emplace( 1126 "Value read from Primary: ", 1127 vpdStream.str()); 1128 1129 createPEL(additionalData, PelSeverity::WARNING, 1130 errIntfForVPDMismatch, nullptr); 1131 1132 if (!isBackupOnCache) 1133 { 1134 // Backing up or restoring from a hardware 1135 // path does not requires copying the backup 1136 // data to the VPD map, as this will result 1137 // in a mismatch between the primary VPD and 1138 // its cache. 1139 continue; 1140 } 1141 } 1142 else 1143 { 1144 // both the backup and primary data is 1145 // non-default and same. Nothing needs to be 1146 // done. 1147 continue; 1148 } 1149 } 1150 1151 // If the backup is on the cache we need to copy the 1152 // backup data to the VPD map to ensure there is no 1153 // mimatch b/n them. So if backup data is not default, 1154 // then irrespective of primary data(default or other 1155 // than backup), copy the backup data to vpd map as we 1156 // don't need to change the backup data in either case 1157 // in the process of restoring system vpd. 1158 kwdValue = backupValue; 1159 1160 // If the backup data is on the base panel the restoring 1161 // of Backup VPD on to the system backplane VPD 1162 // file is done here not through the VPD manager code 1163 // path. This is to have the logic of restoring data on 1164 // to the cache & hardware in the same code path. 1165 if (!isBackupOnCache) 1166 { 1167 // copy backup VPD on to system backplane 1168 // EEPROM file. 1169 updateVpdDataOnHw(systemVpdFilePath, js, recordName, 1170 keywordName, backupDataInBinary); 1171 } 1172 } 1173 else if (kwdDataInBinary == defaultValue && 1174 get<2>(keywordInfo)) // Check isPELRequired is true 1175 { 1176 string errMsg = "Found default value on both backup " 1177 "and primary VPD for record: "; 1178 errMsg += (*it).first; 1179 errMsg += " and keyword: "; 1180 errMsg += keywordName; 1181 errMsg += ". Update primary VPD."; 1182 1183 // mfg default on both backup and primary, log PEL 1184 PelAdditionalData additionalData; 1185 additionalData.emplace("DESCRIPTION", errMsg); 1186 1187 createPEL(additionalData, PelSeverity::ERROR, 1188 errIntfForVPDDefault, nullptr); 1189 1190 continue; 1191 } 1192 else if ((kwdDataInBinary != defaultValue) && 1193 (!isBackupOnCache)) 1194 { 1195 // update primary VPD on to backup VPD file 1196 updateVpdDataOnHw(systemVpdBackupPath, js, 1197 backupVpdRecName, backupVpdKwName, 1198 kwdDataInBinary); 1199 1200 // copy primary VPD to backup VPD to publish on 1201 // DBus 1202 backupVpdMap.find(backupVpdRecName) 1203 ->second.find(backupVpdKwName) 1204 ->second = kwdValue; 1205 } 1206 } 1207 } 1208 } 1209 } 1210 } 1211 1212 /** 1213 * @brief This checks for is this FRU a processor 1214 * And if yes, then checks for is this primary 1215 * 1216 * @param[in] js- vpd json to get the information about this FRU 1217 * @param[in] filePath- FRU vpd 1218 * 1219 * @return true/false 1220 */ 1221 bool isThisPrimaryProcessor(nlohmann::json& js, const string& filePath) 1222 { 1223 bool isProcessor = false; 1224 bool isPrimary = false; 1225 1226 for (const auto& item : js["frus"][filePath]) 1227 { 1228 if (item.find("extraInterfaces") != item.end()) 1229 { 1230 for (const auto& eI : item["extraInterfaces"].items()) 1231 { 1232 if (eI.key().find("Inventory.Item.Cpu") != string::npos) 1233 { 1234 isProcessor = true; 1235 } 1236 } 1237 } 1238 1239 if (isProcessor) 1240 { 1241 string cpuType = item.value("cpuType", ""); 1242 if (cpuType == "primary") 1243 { 1244 isPrimary = true; 1245 } 1246 } 1247 } 1248 1249 return (isProcessor && isPrimary); 1250 } 1251 1252 /** 1253 * @brief This finds DIMM vpd in vpd json and enables them by binding the device 1254 * driver 1255 * @param[in] js- vpd json to iterate through and take action if it is DIMM 1256 */ 1257 void doEnableAllDimms(nlohmann::json& js) 1258 { 1259 // iterate over each fru 1260 for (const auto& eachFru : js["frus"].items()) 1261 { 1262 // skip the driver binding if eeprom already exists 1263 if (fs::exists(eachFru.key())) 1264 { 1265 continue; 1266 } 1267 1268 for (const auto& eachInventory : eachFru.value()) 1269 { 1270 if (eachInventory.find("extraInterfaces") != eachInventory.end()) 1271 { 1272 for (const auto& eI : eachInventory["extraInterfaces"].items()) 1273 { 1274 if (eI.key().find("Inventory.Item.Dimm") != string::npos) 1275 { 1276 string dimmVpd = eachFru.key(); 1277 // fetch it from 1278 // "/sys/bus/i2c/drivers/at24/414-0050/eeprom" 1279 1280 regex matchPatern("([0-9]+-[0-9]{4})"); 1281 smatch matchFound; 1282 if (regex_search(dimmVpd, matchFound, matchPatern)) 1283 { 1284 vector<string> i2cReg; 1285 boost::split(i2cReg, matchFound.str(0), 1286 boost::is_any_of("-")); 1287 1288 // remove 0s from begining 1289 const regex pattern("^0+(?!$)"); 1290 for (auto& i : i2cReg) 1291 { 1292 i = regex_replace(i, pattern, ""); 1293 } 1294 1295 if (i2cReg.size() == 2) 1296 { 1297 // echo 24c32 0x50 > 1298 // /sys/bus/i2c/devices/i2c-16/new_device 1299 string cmnd = "echo 24c32 0x" + i2cReg[1] + 1300 " > /sys/bus/i2c/devices/i2c-" + 1301 i2cReg[0] + "/new_device"; 1302 1303 executeCmd(cmnd); 1304 } 1305 } 1306 } 1307 } 1308 } 1309 } 1310 } 1311 } 1312 1313 /** 1314 * @brief Check if the given CPU is an IO only chip. 1315 * The CPU is termed as IO, whose all of the cores are bad and can never be 1316 * used. Those CPU chips can be used for IO purpose like connecting PCIe devices 1317 * etc., The CPU whose every cores are bad, can be identified from the CP00 1318 * record's PG keyword, only if all of the 8 EQs' value equals 0xE7F9FF. (1EQ 1319 * has 4 cores grouped together by sharing its cache memory.) 1320 * @param [in] pgKeyword - PG Keyword of CPU. 1321 * @return true if the given cpu is an IO, false otherwise. 1322 */ 1323 static bool isCPUIOGoodOnly(const string& pgKeyword) 1324 { 1325 const unsigned char io[] = {0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 1326 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 1327 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF}; 1328 // EQ0 index (in PG keyword) starts at 97 (with offset starting from 0). 1329 // Each EQ carries 3 bytes of data. Totally there are 8 EQs. If all EQs' 1330 // value equals 0xE7F9FF, then the cpu has no good cores and its treated as 1331 // IO. 1332 if (memcmp(io, pgKeyword.data() + 97, 24) == 0) 1333 { 1334 return true; 1335 } 1336 1337 // The CPU is not an IO 1338 return false; 1339 } 1340 1341 /** 1342 * @brief Populate Dbus. 1343 * This method invokes all the populateInterface functions 1344 * and notifies PIM about dbus object. 1345 * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the 1346 * input. 1347 * @param[in] js - Inventory json object 1348 * @param[in] filePath - Path of the vpd file 1349 * @param[in] preIntrStr - Interface string 1350 */ 1351 template <typename T> 1352 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath) 1353 { 1354 inventory::InterfaceMap interfaces; 1355 inventory::ObjectMap objects; 1356 inventory::PropertyMap prop; 1357 string ccinFromVpd; 1358 1359 bool isSystemVpd = (filePath == systemVpdFilePath); 1360 if constexpr (is_same<T, Parsed>::value) 1361 { 1362 ccinFromVpd = getKwVal(vpdMap, "VINI", "CC"); 1363 transform(ccinFromVpd.begin(), ccinFromVpd.end(), ccinFromVpd.begin(), 1364 ::toupper); 1365 1366 if (isSystemVpd) 1367 { 1368 string mboardPath = 1369 js["frus"][filePath].at(0).value("inventoryPath", ""); 1370 1371 // Get the value of systemvpdBackupPath field from json 1372 const std::string& systemVpdBackupPath = 1373 js["frus"][filePath].at(0).value("systemVpdBackupPath", ""); 1374 1375 if (systemVpdBackupPath.empty()) 1376 { 1377 std::vector<std::string> interfaces = {motherBoardInterface}; 1378 // call mapper to check for object path creation 1379 MapperResponse subTree = 1380 getObjectSubtreeForInterfaces(pimPath, 0, interfaces); 1381 1382 // Attempt system VPD restore if we have a motherboard 1383 // object in the inventory. 1384 if ((subTree.size() != 0) && 1385 (subTree.find(pimPath + mboardPath) != subTree.end())) 1386 { 1387 restoreSystemVPD(vpdMap, mboardPath, js); 1388 } 1389 else 1390 { 1391 log<level::ERR>("No object path found"); 1392 } 1393 } 1394 else 1395 { 1396 restoreSystemVPD(vpdMap, mboardPath, js, false); 1397 } 1398 } 1399 else 1400 { 1401 // check if it is processor vpd. 1402 auto isPrimaryCpu = isThisPrimaryProcessor(js, filePath); 1403 1404 if (isPrimaryCpu) 1405 { 1406 auto ddVersion = getKwVal(vpdMap, "CRP0", "DD"); 1407 1408 auto chipVersion = atoi(ddVersion.substr(1, 2).c_str()); 1409 1410 if (chipVersion >= 2) 1411 { 1412 doEnableAllDimms(js); 1413 // Sleep for a few seconds to let the DIMM parses start 1414 using namespace std::chrono_literals; 1415 std::this_thread::sleep_for(5s); 1416 } 1417 } 1418 } 1419 } 1420 1421 auto processFactoryReset = false; 1422 1423 if (isSystemVpd) 1424 { 1425 string systemJsonName{}; 1426 if constexpr (is_same<T, Parsed>::value) 1427 { 1428 // pick the right system json 1429 systemJsonName = getSystemsJson(vpdMap); 1430 } 1431 1432 fs::path target = systemJsonName; 1433 fs::path link = INVENTORY_JSON_SYM_LINK; 1434 1435 // If the symlink does not exist, we treat that as a factory reset 1436 processFactoryReset = !fs::exists(INVENTORY_JSON_SYM_LINK); 1437 1438 // Create the directory for hosting the symlink 1439 fs::create_directories(VPD_FILES_PATH); 1440 // unlink the symlink previously created (if any) 1441 remove(INVENTORY_JSON_SYM_LINK); 1442 // create a new symlink based on the system 1443 fs::create_symlink(target, link); 1444 1445 // Reloading the json 1446 ifstream inventoryJson(link); 1447 js = json::parse(inventoryJson); 1448 inventoryJson.close(); 1449 } 1450 1451 for (const auto& item : js["frus"][filePath]) 1452 { 1453 const auto& objectPath = item["inventoryPath"]; 1454 sdbusplus::message::object_path object(objectPath); 1455 1456 vector<string> ccinList; 1457 if (item.find("ccin") != item.end()) 1458 { 1459 for (const auto& cc : item["ccin"]) 1460 { 1461 string ccin = cc; 1462 transform(ccin.begin(), ccin.end(), ccin.begin(), ::toupper); 1463 ccinList.push_back(ccin); 1464 } 1465 } 1466 1467 if (!ccinFromVpd.empty() && !ccinList.empty() && 1468 (find(ccinList.begin(), ccinList.end(), ccinFromVpd) == 1469 ccinList.end())) 1470 { 1471 continue; 1472 } 1473 1474 if ((isSystemVpd) || (item.value("noprime", false))) 1475 { 1476 // Populate one time properties for the system VPD and its sub-frus 1477 // and for other non-primeable frus. 1478 // For the remaining FRUs, this will get handled as a part of 1479 // priming the inventory. 1480 setOneTimeProperties(objectPath, interfaces); 1481 } 1482 1483 // Populate the VPD keywords and the common interfaces only if we 1484 // are asked to inherit that data from the VPD, else only add the 1485 // extraInterfaces. 1486 if (item.value("inherit", true)) 1487 { 1488 if constexpr (is_same<T, Parsed>::value) 1489 { 1490 // Each record in the VPD becomes an interface and all 1491 // keyword within the record are properties under that 1492 // interface. 1493 for (const auto& record : vpdMap) 1494 { 1495 populateFruSpecificInterfaces( 1496 record.second, ipzVpdInf + record.first, interfaces); 1497 } 1498 } 1499 else if constexpr (is_same<T, KeywordVpdMap>::value) 1500 { 1501 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces); 1502 } 1503 if (js.find("commonInterfaces") != js.end()) 1504 { 1505 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, 1506 isSystemVpd); 1507 } 1508 } 1509 else 1510 { 1511 // Check if we have been asked to inherit specific record(s) 1512 if constexpr (is_same<T, Parsed>::value) 1513 { 1514 if (item.find("copyRecords") != item.end()) 1515 { 1516 for (const auto& record : item["copyRecords"]) 1517 { 1518 const string& recordName = record; 1519 if (vpdMap.find(recordName) != vpdMap.end()) 1520 { 1521 populateFruSpecificInterfaces( 1522 vpdMap.at(recordName), ipzVpdInf + recordName, 1523 interfaces); 1524 } 1525 } 1526 } 1527 } 1528 } 1529 // Populate interfaces and properties that are common to every FRU 1530 // and additional interface that might be defined on a per-FRU 1531 // basis. 1532 if (item.find("extraInterfaces") != item.end()) 1533 { 1534 populateInterfaces(item["extraInterfaces"], interfaces, vpdMap, 1535 isSystemVpd); 1536 if constexpr (is_same<T, Parsed>::value) 1537 { 1538 if (item["extraInterfaces"].find( 1539 "xyz.openbmc_project.Inventory.Item.Cpu") != 1540 item["extraInterfaces"].end()) 1541 { 1542 if (isCPUIOGoodOnly(getKwVal(vpdMap, "CP00", "PG"))) 1543 { 1544 interfaces[invItemIntf]["PrettyName"] = "IO Module"; 1545 } 1546 } 1547 } 1548 } 1549 1550 // embedded property(true or false) says whether the subfru is embedded 1551 // into the parent fru (or) not. VPD sets Present property only for 1552 // embedded frus. If the subfru is not an embedded FRU, the subfru may 1553 // or may not be physically present. Those non embedded frus will always 1554 // have Present=false irrespective of its physical presence or absence. 1555 // Eg: nvme drive in nvme slot is not an embedded FRU. So don't set 1556 // Present to true for such sub frus. 1557 // Eg: ethernet port is embedded into bmc card. So set Present to true 1558 // for such sub frus. Also donot populate present property for embedded 1559 // subfru which is synthesized. Currently there is no subfru which are 1560 // both embedded and synthesized. But still the case is handled here. 1561 if ((item.value("embedded", true)) && 1562 (!item.value("synthesized", false))) 1563 { 1564 // Check if its required to handle presence for this FRU. 1565 if (item.value("handlePresence", true)) 1566 { 1567 inventory::PropertyMap presProp; 1568 presProp.emplace("Present", true); 1569 insertOrMerge(interfaces, invItemIntf, move(presProp)); 1570 } 1571 } 1572 1573 if constexpr (is_same<T, Parsed>::value) 1574 { 1575 // Restore asset tag, if needed 1576 if (processFactoryReset && objectPath == "/system") 1577 { 1578 fillAssetTag(interfaces, vpdMap); 1579 } 1580 } 1581 1582 objects.emplace(move(object), move(interfaces)); 1583 } 1584 1585 if (isSystemVpd) 1586 { 1587 inventory::ObjectMap primeObject = primeInventory(js, vpdMap); 1588 objects.insert(primeObject.begin(), primeObject.end()); 1589 1590 // set the U-boot environment variable for device-tree 1591 if constexpr (is_same<T, Parsed>::value) 1592 { 1593 setDevTreeEnv(fs::path(getSystemsJson(vpdMap)).filename()); 1594 } 1595 } 1596 1597 // Notify PIM 1598 common::utility::callPIM(move(objects)); 1599 } 1600 1601 int main(int argc, char** argv) 1602 { 1603 int rc = 0; 1604 json js{}; 1605 Binary vpdVector{}; 1606 string file{}; 1607 string driver{}; 1608 // map to hold additional data in case of logging pel 1609 PelAdditionalData additionalData{}; 1610 1611 // this is needed to hold base fru inventory path in case there is ECC or 1612 // vpd exception while parsing the file 1613 std::string baseFruInventoryPath = {}; 1614 1615 // It holds the backup EEPROM file path for the system backplane's critical 1616 // data 1617 std::string systemVpdBackupPath{}; 1618 1619 // It holds the inventory path of backup EEPROM file 1620 std::string backupVpdInvPath{}; 1621 1622 bool isSystemVpd = false; 1623 1624 // severity for PEL 1625 PelSeverity pelSeverity = PelSeverity::WARNING; 1626 1627 try 1628 { 1629 App app{"ibm-read-vpd - App to read IPZ/Jedec format VPD, parse it and " 1630 "store it in DBUS"}; 1631 1632 app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") 1633 ->required(); 1634 1635 app.add_option("--driver", driver, 1636 "Driver used by kernel (at24,at25,ee1004)") 1637 ->required(); 1638 1639 CLI11_PARSE(app, argc, argv); 1640 1641 // PEL severity should be ERROR in case of any system VPD failure 1642 if (file == systemVpdFilePath) 1643 { 1644 pelSeverity = PelSeverity::ERROR; 1645 isSystemVpd = true; 1646 } 1647 1648 // Check if input file is not empty. 1649 if ((file.empty()) || (driver.empty())) 1650 { 1651 std::cerr << "Encountered empty input parameter file [" << file 1652 << "] driver [" << driver << "]" << std::endl; 1653 return 0; 1654 } 1655 1656 // Check if currently supported driver or not 1657 if ((driver != at24driver) && (driver != at25driver) && 1658 (driver != ee1004driver)) 1659 { 1660 std::cerr << "The driver [" << driver << "] is not supported." 1661 << std::endl; 1662 return 0; 1663 } 1664 1665 auto jsonToParse = INVENTORY_JSON_DEFAULT; 1666 1667 // If the symlink exists, it means it has been setup for us, switch the 1668 // path 1669 if (fs::exists(INVENTORY_JSON_SYM_LINK)) 1670 { 1671 jsonToParse = INVENTORY_JSON_SYM_LINK; 1672 } 1673 1674 // Make sure that the file path we get is for a supported EEPROM 1675 ifstream inventoryJson(jsonToParse); 1676 if (!inventoryJson) 1677 { 1678 throw(VpdJsonException("Failed to access Json path", jsonToParse)); 1679 } 1680 1681 try 1682 { 1683 js = json::parse(inventoryJson); 1684 } 1685 catch (const json::parse_error& ex) 1686 { 1687 throw(VpdJsonException("Json parsing failed", jsonToParse)); 1688 } 1689 1690 // Do we have the mandatory "frus" section? 1691 if (js.find("frus") == js.end()) 1692 { 1693 throw(VpdJsonException("FRUs section not found in JSON", 1694 jsonToParse)); 1695 } 1696 1697 // Check if it's a udev path - patterned as(/ahb/ahb:apb/ahb:apb:bus@) 1698 if (file.find("/ahb:apb") != string::npos) 1699 { 1700 // Translate udev path to a generic /sys/bus/.. file path. 1701 udevToGenericPath(file, driver); 1702 1703 if ((js["frus"].find(file) != js["frus"].end()) && 1704 (file == systemVpdFilePath)) 1705 { 1706 std::cout << "We have already collected system VPD, skiping." 1707 << std::endl; 1708 return 0; 1709 } 1710 } 1711 1712 if (file.empty()) 1713 { 1714 std::cerr << "The EEPROM path <" << file << "> is not valid."; 1715 return 0; 1716 } 1717 if (js["frus"].find(file) == js["frus"].end()) 1718 { 1719 std::cerr << "The EEPROM path [" << file 1720 << "] is not found in the json." << std::endl; 1721 return 0; 1722 } 1723 1724 if (!fs::exists(file)) 1725 { 1726 std::cout << "Device path: " << file 1727 << " does not exist. Spurious udev event? Exiting." 1728 << std::endl; 1729 return 0; 1730 } 1731 1732 // In case of system VPD it will already be filled, Don't have to 1733 // overwrite that. 1734 if (baseFruInventoryPath.empty()) 1735 { 1736 baseFruInventoryPath = js["frus"][file][0]["inventoryPath"]; 1737 } 1738 1739 // Check if we can read the VPD file based on the power state 1740 // We skip reading VPD when the power is ON in two scenarios: 1741 // 1) The eeprom we are trying to read is that of the system VPD and the 1742 // JSON symlink is already setup (the symlink's existence tells us we 1743 // are not coming out of a factory reset) 1744 // 2) The JSON tells us that the FRU EEPROM cannot be 1745 // read when we are powered ON. 1746 if (js["frus"][file].at(0).value("powerOffOnly", false) || 1747 (file == systemVpdFilePath && fs::exists(INVENTORY_JSON_SYM_LINK))) 1748 { 1749 if ("xyz.openbmc_project.State.Chassis.PowerState.On" == 1750 getPowerState()) 1751 { 1752 std::cout << "This VPD cannot be read when power is ON" 1753 << std::endl; 1754 return 0; 1755 } 1756 } 1757 1758 // Check if this VPD should be recollected at all 1759 if (!needsRecollection(js, file)) 1760 { 1761 std::cout << "Skip VPD recollection for: " << file << std::endl; 1762 return 0; 1763 } 1764 1765 try 1766 { 1767 variant<KeywordVpdMap, Store> parseResult; 1768 parseResult = parseVpdFile(file, js); 1769 1770 if (isSystemVpd) 1771 { 1772 // Get the value of systemVpdBackupPath field from json 1773 systemVpdBackupPath = js["frus"][systemVpdFilePath].at(0).value( 1774 "systemVpdBackupPath", ""); 1775 1776 if (!systemVpdBackupPath.empty()) 1777 { 1778 backupVpdInvPath = 1779 js["frus"][systemVpdBackupPath][0]["inventoryPath"] 1780 .get_ref<const nlohmann::json::string_t&>(); 1781 } 1782 } 1783 1784 if (auto pVal = get_if<Store>(&parseResult)) 1785 { 1786 populateDbus(pVal->getVpdMap(), js, file); 1787 } 1788 else if (auto pVal = get_if<KeywordVpdMap>(&parseResult)) 1789 { 1790 populateDbus(*pVal, js, file); 1791 } 1792 } 1793 catch (const exception& e) 1794 { 1795 if (!systemVpdBackupPath.empty()) 1796 { 1797 file = systemVpdBackupPath; 1798 baseFruInventoryPath = backupVpdInvPath; 1799 } 1800 1801 executePostFailAction(js, file); 1802 throw; 1803 } 1804 } 1805 catch (const VpdJsonException& ex) 1806 { 1807 additionalData.emplace("JSON_PATH", ex.getJsonPath()); 1808 additionalData.emplace("DESCRIPTION", ex.what()); 1809 createPEL(additionalData, pelSeverity, errIntfForJsonFailure, nullptr); 1810 1811 std::cerr << ex.what() << "\n"; 1812 rc = -1; 1813 } 1814 catch (const VpdEccException& ex) 1815 { 1816 additionalData.emplace("DESCRIPTION", "ECC check failed"); 1817 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1818 INVENTORY_PATH + baseFruInventoryPath); 1819 createPEL(additionalData, pelSeverity, errIntfForEccCheckFail, nullptr); 1820 1821 if (systemVpdBackupPath.empty()) 1822 { 1823 dumpBadVpd(file, vpdVector); 1824 } 1825 1826 std::cerr << ex.what() << "\n"; 1827 rc = -1; 1828 } 1829 catch (const VpdDataException& ex) 1830 { 1831 if (isThisPcieOnPass1planar(js, file)) 1832 { 1833 std::cout << "Pcie_device [" << file 1834 << "]'s VPD is not valid on PASS1 planar.Ignoring.\n"; 1835 rc = 0; 1836 } 1837 else if (!(isPresent(js, file).value_or(true))) 1838 { 1839 std::cout << "FRU at: " << file 1840 << " is not detected present. Ignore parser error.\n"; 1841 rc = 0; 1842 } 1843 else 1844 { 1845 string errorMsg = 1846 "VPD file is either empty or invalid. Parser failed for ["; 1847 errorMsg += file; 1848 errorMsg += "], with error = " + std::string(ex.what()); 1849 1850 additionalData.emplace("DESCRIPTION", errorMsg); 1851 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1852 INVENTORY_PATH + baseFruInventoryPath); 1853 createPEL(additionalData, pelSeverity, errIntfForInvalidVPD, 1854 nullptr); 1855 1856 rc = -1; 1857 } 1858 } 1859 catch (const exception& e) 1860 { 1861 dumpBadVpd(file, vpdVector); 1862 std::cerr << e.what() << "\n"; 1863 rc = -1; 1864 } 1865 1866 return rc; 1867 } 1868