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