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