1 #include "config.h" 2 3 #include "common_utility.hpp" 4 #include "defines.hpp" 5 #include "ibm_vpd_utils.hpp" 6 #include "ipz_parser.hpp" 7 #include "keyword_vpd_parser.hpp" 8 #include "memory_vpd_parser.hpp" 9 #include "parser_factory.hpp" 10 #include "vpd_exceptions.hpp" 11 12 #include <assert.h> 13 #include <ctype.h> 14 15 #include <CLI/CLI.hpp> 16 #include <algorithm> 17 #include <boost/algorithm/string.hpp> 18 #include <cstdarg> 19 #include <exception> 20 #include <filesystem> 21 #include <fstream> 22 #include <gpiod.hpp> 23 #include <iostream> 24 #include <iterator> 25 #include <nlohmann/json.hpp> 26 #include <phosphor-logging/log.hpp> 27 #include <regex> 28 29 using namespace std; 30 using namespace openpower::vpd; 31 using namespace CLI; 32 using namespace vpd::keyword::parser; 33 using namespace openpower::vpd::constants; 34 namespace fs = filesystem; 35 using json = nlohmann::json; 36 using namespace openpower::vpd::parser::factory; 37 using namespace openpower::vpd::inventory; 38 using namespace openpower::vpd::memory::parser; 39 using namespace openpower::vpd::parser::interface; 40 using namespace openpower::vpd::exceptions; 41 using namespace phosphor::logging; 42 43 static const deviceTreeMap deviceTreeSystemTypeMap = { 44 {RAINIER_2U, "conf-aspeed-bmc-ibm-rainier-p1.dtb"}, 45 {RAINIER_2U_V2, "conf-aspeed-bmc-ibm-rainier.dtb"}, 46 {RAINIER_4U, "conf-aspeed-bmc-ibm-rainier-4u-p1.dtb"}, 47 {RAINIER_4U_V2, "conf-aspeed-bmc-ibm-rainier-4u.dtb"}, 48 {RAINIER_1S4U, "conf-aspeed-bmc-ibm-rainier-1s4u.dtb"}, 49 {EVEREST, "conf-aspeed-bmc-ibm-everest.dtb"}}; 50 51 /** 52 * @brief Returns the power state for chassis0 53 */ 54 static auto getPowerState() 55 { 56 // TODO: How do we handle multiple chassis? 57 string powerState{}; 58 auto bus = sdbusplus::bus::new_default(); 59 auto properties = 60 bus.new_method_call("xyz.openbmc_project.State.Chassis", 61 "/xyz/openbmc_project/state/chassis0", 62 "org.freedesktop.DBus.Properties", "Get"); 63 properties.append("xyz.openbmc_project.State.Chassis"); 64 properties.append("CurrentPowerState"); 65 auto result = bus.call(properties); 66 if (!result.is_method_error()) 67 { 68 variant<string> val; 69 result.read(val); 70 if (auto pVal = get_if<string>(&val)) 71 { 72 powerState = *pVal; 73 } 74 } 75 cout << "Power state is: " << powerState << endl; 76 return powerState; 77 } 78 79 /** 80 * @brief Expands location codes 81 */ 82 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap, 83 bool isSystemVpd) 84 { 85 auto expanded{unexpanded}; 86 static constexpr auto SYSTEM_OBJECT = "/system/chassis/motherboard"; 87 static constexpr auto VCEN_IF = "com.ibm.ipzvpd.VCEN"; 88 static constexpr auto VSYS_IF = "com.ibm.ipzvpd.VSYS"; 89 size_t idx = expanded.find("fcs"); 90 try 91 { 92 if (idx != string::npos) 93 { 94 string fc{}; 95 string se{}; 96 if (isSystemVpd) 97 { 98 const auto& fcData = vpdMap.at("VCEN").at("FC"); 99 const auto& seData = vpdMap.at("VCEN").at("SE"); 100 fc = string(fcData.data(), fcData.size()); 101 se = string(seData.data(), seData.size()); 102 } 103 else 104 { 105 fc = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "FC"); 106 se = readBusProperty(SYSTEM_OBJECT, VCEN_IF, "SE"); 107 } 108 109 // TODO: See if ND0 can be placed in the JSON 110 expanded.replace(idx, 3, fc.substr(0, 4) + ".ND0." + se); 111 } 112 else 113 { 114 idx = expanded.find("mts"); 115 if (idx != string::npos) 116 { 117 string mt{}; 118 string se{}; 119 if (isSystemVpd) 120 { 121 const auto& mtData = vpdMap.at("VSYS").at("TM"); 122 const auto& seData = vpdMap.at("VSYS").at("SE"); 123 mt = string(mtData.data(), mtData.size()); 124 se = string(seData.data(), seData.size()); 125 } 126 else 127 { 128 mt = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "TM"); 129 se = readBusProperty(SYSTEM_OBJECT, VSYS_IF, "SE"); 130 } 131 132 replace(mt.begin(), mt.end(), '-', '.'); 133 expanded.replace(idx, 3, mt + "." + se); 134 } 135 } 136 } 137 catch (const exception& e) 138 { 139 cerr << "Failed to expand location code with exception: " << e.what() 140 << "\n"; 141 } 142 return expanded; 143 } 144 145 /** 146 * @brief Populate FRU specific interfaces. 147 * 148 * This is a common method which handles both 149 * ipz and keyword specific interfaces thus, 150 * reducing the code redundancy. 151 * @param[in] map - Reference to the innermost keyword-value map. 152 * @param[in] preIntrStr - Reference to the interface string. 153 * @param[out] interfaces - Reference to interface map. 154 */ 155 template <typename T> 156 static void populateFruSpecificInterfaces(const T& map, 157 const string& preIntrStr, 158 inventory::InterfaceMap& interfaces) 159 { 160 inventory::PropertyMap prop; 161 162 for (const auto& kwVal : map) 163 { 164 auto kw = kwVal.first; 165 166 if (kw[0] == '#') 167 { 168 kw = string("PD_") + kw[1]; 169 } 170 else if (isdigit(kw[0])) 171 { 172 kw = string("N_") + kw; 173 } 174 if constexpr (is_same<T, KeywordVpdMap>::value) 175 { 176 if (get_if<Binary>(&kwVal.second)) 177 { 178 Binary vec(get_if<Binary>(&kwVal.second)->begin(), 179 get_if<Binary>(&kwVal.second)->end()); 180 prop.emplace(move(kw), move(vec)); 181 } 182 else 183 { 184 if (kw == "MemorySizeInKB") 185 { 186 inventory::PropertyMap memProp; 187 auto memVal = get_if<size_t>(&kwVal.second); 188 if (memVal) 189 { 190 memProp.emplace(move(kw), 191 ((*memVal) * CONVERT_MB_TO_KB)); 192 interfaces.emplace( 193 "xyz.openbmc_project.Inventory.Item.Dimm", 194 move(memProp)); 195 } 196 else 197 { 198 cerr << "MemorySizeInKB value not found in vpd map\n"; 199 } 200 } 201 } 202 } 203 else 204 { 205 Binary vec(kwVal.second.begin(), kwVal.second.end()); 206 prop.emplace(move(kw), move(vec)); 207 } 208 } 209 210 interfaces.emplace(preIntrStr, move(prop)); 211 } 212 213 /** 214 * @brief Populate Interfaces. 215 * 216 * This method populates common and extra interfaces to dbus. 217 * @param[in] js - json object 218 * @param[out] interfaces - Reference to interface map 219 * @param[in] vpdMap - Reference to the parsed vpd map. 220 * @param[in] isSystemVpd - Denotes whether we are collecting the system VPD. 221 */ 222 template <typename T> 223 static void populateInterfaces(const nlohmann::json& js, 224 inventory::InterfaceMap& interfaces, 225 const T& vpdMap, bool isSystemVpd) 226 { 227 for (const auto& ifs : js.items()) 228 { 229 string inf = ifs.key(); 230 inventory::PropertyMap props; 231 232 for (const auto& itr : ifs.value().items()) 233 { 234 const string& busProp = itr.key(); 235 236 if (itr.value().is_boolean()) 237 { 238 props.emplace(busProp, itr.value().get<bool>()); 239 } 240 else if (itr.value().is_string()) 241 { 242 if constexpr (is_same<T, Parsed>::value) 243 { 244 if (busProp == "LocationCode" && 245 inf == IBM_LOCATION_CODE_INF) 246 { 247 // TODO deprecate the com.ibm interface later 248 auto prop = expandLocationCode( 249 itr.value().get<string>(), vpdMap, isSystemVpd); 250 props.emplace(busProp, prop); 251 interfaces.emplace(XYZ_LOCATION_CODE_INF, props); 252 } 253 else 254 { 255 props.emplace(busProp, itr.value().get<string>()); 256 } 257 } 258 else 259 { 260 props.emplace(busProp, itr.value().get<string>()); 261 } 262 } 263 else if (itr.value().is_array()) 264 { 265 try 266 { 267 props.emplace(busProp, itr.value().get<Binary>()); 268 } 269 catch (const nlohmann::detail::type_error& e) 270 { 271 std::cerr << "Type exception: " << e.what() << "\n"; 272 // Ignore any type errors 273 } 274 } 275 else if (itr.value().is_object()) 276 { 277 const string& rec = itr.value().value("recordName", ""); 278 const string& kw = itr.value().value("keywordName", ""); 279 const string& encoding = itr.value().value("encoding", ""); 280 281 if constexpr (is_same<T, Parsed>::value) 282 { 283 if (!rec.empty() && !kw.empty() && vpdMap.count(rec) && 284 vpdMap.at(rec).count(kw)) 285 { 286 auto encoded = 287 encodeKeyword(vpdMap.at(rec).at(kw), encoding); 288 props.emplace(busProp, encoded); 289 } 290 } 291 else if constexpr (is_same<T, KeywordVpdMap>::value) 292 { 293 if (!kw.empty() && vpdMap.count(kw)) 294 { 295 auto kwValue = get_if<Binary>(&vpdMap.at(kw)); 296 auto uintValue = get_if<size_t>(&vpdMap.at(kw)); 297 298 if (kwValue) 299 { 300 auto prop = 301 string((*kwValue).begin(), (*kwValue).end()); 302 303 auto encoded = encodeKeyword(prop, encoding); 304 305 props.emplace(busProp, encoded); 306 } 307 else if (uintValue) 308 { 309 props.emplace(busProp, *uintValue); 310 } 311 } 312 } 313 } 314 else if (itr.value().is_number()) 315 { 316 // For now assume the value is a size_t. In the future it would 317 // be nice to come up with a way to get the type from the JSON. 318 props.emplace(busProp, itr.value().get<size_t>()); 319 } 320 } 321 interfaces.emplace(inf, move(props)); 322 } 323 } 324 325 static Binary getVpdDataInVector(const nlohmann::json& js, const string& file) 326 { 327 uint32_t offset = 0; 328 // check if offset present? 329 for (const auto& item : js["frus"][file]) 330 { 331 if (item.find("offset") != item.end()) 332 { 333 offset = item["offset"]; 334 } 335 } 336 337 // TODO: Figure out a better way to get max possible VPD size. 338 Binary vpdVector; 339 vpdVector.resize(65504); 340 ifstream vpdFile; 341 vpdFile.open(file, ios::binary); 342 343 vpdFile.seekg(offset, ios_base::cur); 344 vpdFile.read(reinterpret_cast<char*>(&vpdVector[0]), 65504); 345 vpdVector.resize(vpdFile.gcount()); 346 347 return vpdVector; 348 } 349 350 /** This API will be called at the end of VPD collection to perform any post 351 * actions. 352 * 353 * @param[in] json - json object 354 * @param[in] file - eeprom file path 355 */ 356 static void postFailAction(const nlohmann::json& json, const string& file) 357 { 358 if ((json["frus"][file].at(0)).find("postActionFail") == 359 json["frus"][file].at(0).end()) 360 { 361 return; 362 } 363 364 uint8_t pinValue = 0; 365 string pinName; 366 367 for (const auto& postAction : 368 (json["frus"][file].at(0))["postActionFail"].items()) 369 { 370 if (postAction.key() == "pin") 371 { 372 pinName = postAction.value(); 373 } 374 else if (postAction.key() == "value") 375 { 376 // Get the value to set 377 pinValue = postAction.value(); 378 } 379 } 380 381 cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl; 382 383 try 384 { 385 gpiod::line outputLine = gpiod::find_line(pinName); 386 387 if (!outputLine) 388 { 389 cout << "Couldn't find output line:" << pinName 390 << " on GPIO. Skipping...\n"; 391 392 return; 393 } 394 outputLine.request( 395 {"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0}, 396 pinValue); 397 } 398 catch (const system_error&) 399 { 400 cerr << "Failed to set post-action GPIO" << endl; 401 } 402 } 403 404 /** Performs any pre-action needed to get the FRU setup for collection. 405 * 406 * @param[in] json - json object 407 * @param[in] file - eeprom file path 408 */ 409 static void preAction(const nlohmann::json& json, const string& file) 410 { 411 if ((json["frus"][file].at(0)).find("preAction") == 412 json["frus"][file].at(0).end()) 413 { 414 return; 415 } 416 417 uint8_t pinValue = 0; 418 string pinName; 419 420 for (const auto& postAction : 421 (json["frus"][file].at(0))["preAction"].items()) 422 { 423 if (postAction.key() == "pin") 424 { 425 pinName = postAction.value(); 426 } 427 else if (postAction.key() == "value") 428 { 429 // Get the value to set 430 pinValue = postAction.value(); 431 } 432 } 433 434 cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl; 435 try 436 { 437 gpiod::line outputLine = gpiod::find_line(pinName); 438 439 if (!outputLine) 440 { 441 cout << "Couldn't find output line:" << pinName 442 << " on GPIO. Skipping...\n"; 443 444 return; 445 } 446 outputLine.request( 447 {"FRU pre-action", ::gpiod::line_request::DIRECTION_OUTPUT, 0}, 448 pinValue); 449 } 450 catch (const system_error&) 451 { 452 cerr << "Failed to set pre-action GPIO" << endl; 453 return; 454 } 455 456 // Now bind the device 457 string bind = json["frus"][file].at(0).value("devAddress", ""); 458 cout << "Binding device " << bind << endl; 459 string bindCmd = string("echo \"") + bind + 460 string("\" > /sys/bus/i2c/drivers/at24/bind"); 461 cout << bindCmd << endl; 462 executeCmd(bindCmd); 463 464 // Check if device showed up (test for file) 465 if (!fs::exists(file)) 466 { 467 cout << "EEPROM " << file << " does not exist. Take failure action" 468 << endl; 469 // If not, then take failure postAction 470 postFailAction(json, file); 471 } 472 } 473 474 /** 475 * @brief Set certain one time properties in the inventory 476 * Use this function to insert the Functional and Enabled properties into the 477 * inventory map. This function first checks if the object in question already 478 * has these properties hosted on D-Bus, if the property is already there, it is 479 * not modified, hence the name "one time". If the property is not already 480 * present, it will be added to the map with a suitable default value (true for 481 * Functional and false for Enabled) 482 * 483 * @param[in] object - The inventory D-Bus obejct without the inventory prefix. 484 * @param[inout] interfaces - Reference to a map of inventory interfaces to 485 * which the properties will be attached. 486 */ 487 static void setOneTimeProperties(const std::string& object, 488 inventory::InterfaceMap& interfaces) 489 { 490 auto bus = sdbusplus::bus::new_default(); 491 auto objectPath = INVENTORY_PATH + object; 492 auto prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager", 493 objectPath.c_str(), 494 "org.freedesktop.DBus.Properties", "Get"); 495 prop.append("xyz.openbmc_project.State.Decorator.OperationalStatus"); 496 prop.append("Functional"); 497 try 498 { 499 auto result = bus.call(prop); 500 } 501 catch (const sdbusplus::exception::SdBusError& e) 502 { 503 // Treat as property unavailable 504 inventory::PropertyMap prop; 505 prop.emplace("Functional", true); 506 interfaces.emplace( 507 "xyz.openbmc_project.State.Decorator.OperationalStatus", 508 move(prop)); 509 } 510 prop = bus.new_method_call("xyz.openbmc_project.Inventory.Manager", 511 objectPath.c_str(), 512 "org.freedesktop.DBus.Properties", "Get"); 513 prop.append("xyz.openbmc_project.Object.Enable"); 514 prop.append("Enabled"); 515 try 516 { 517 auto result = bus.call(prop); 518 } 519 catch (const sdbusplus::exception::SdBusError& e) 520 { 521 // Treat as property unavailable 522 inventory::PropertyMap prop; 523 prop.emplace("Enabled", false); 524 interfaces.emplace("xyz.openbmc_project.Object.Enable", move(prop)); 525 } 526 } 527 528 /** 529 * @brief Prime the Inventory 530 * Prime the inventory by populating only the location code, 531 * type interface and the inventory object for the frus 532 * which are not system vpd fru. 533 * 534 * @param[in] jsObject - Reference to vpd inventory json object 535 * @param[in] vpdMap - Reference to the parsed vpd map 536 * 537 * @returns Map of items in extraInterface. 538 */ 539 template <typename T> 540 inventory::ObjectMap primeInventory(const nlohmann::json& jsObject, 541 const T& vpdMap) 542 { 543 inventory::ObjectMap objects; 544 545 for (auto& itemFRUS : jsObject["frus"].items()) 546 { 547 // Take pre actions 548 preAction(jsObject, itemFRUS.key()); 549 for (auto& itemEEPROM : itemFRUS.value()) 550 { 551 inventory::InterfaceMap interfaces; 552 inventory::Object object(itemEEPROM.at("inventoryPath")); 553 554 if ((itemFRUS.key() != systemVpdFilePath) && 555 !itemEEPROM.value("noprime", false)) 556 { 557 inventory::PropertyMap presProp; 558 presProp.emplace("Present", false); 559 interfaces.emplace("xyz.openbmc_project.Inventory.Item", 560 presProp); 561 setOneTimeProperties(object, interfaces); 562 if (itemEEPROM.find("extraInterfaces") != itemEEPROM.end()) 563 { 564 for (const auto& eI : itemEEPROM["extraInterfaces"].items()) 565 { 566 inventory::PropertyMap props; 567 if (eI.key() == IBM_LOCATION_CODE_INF) 568 { 569 if constexpr (std::is_same<T, Parsed>::value) 570 { 571 for (auto& lC : eI.value().items()) 572 { 573 auto propVal = expandLocationCode( 574 lC.value().get<string>(), vpdMap, true); 575 576 props.emplace(move(lC.key()), 577 move(propVal)); 578 interfaces.emplace(XYZ_LOCATION_CODE_INF, 579 props); 580 interfaces.emplace(move(eI.key()), 581 move(props)); 582 } 583 } 584 } 585 else if (eI.key().find("Inventory.Item.") != 586 string::npos) 587 { 588 interfaces.emplace(move(eI.key()), move(props)); 589 } 590 else if (eI.key() == 591 "xyz.openbmc_project.Inventory.Item") 592 { 593 for (auto& val : eI.value().items()) 594 { 595 if (val.key() == "PrettyName") 596 { 597 presProp.emplace(val.key(), 598 val.value().get<string>()); 599 } 600 } 601 // Use insert_or_assign here as we may already have 602 // inserted the present property only earlier in 603 // this function under this same interface. 604 interfaces.insert_or_assign(eI.key(), 605 move(presProp)); 606 } 607 } 608 } 609 objects.emplace(move(object), move(interfaces)); 610 } 611 } 612 } 613 return objects; 614 } 615 616 /** 617 * @brief This API executes command to set environment variable 618 * And then reboot the system 619 * @param[in] key -env key to set new value 620 * @param[in] value -value to set. 621 */ 622 void setEnvAndReboot(const string& key, const string& value) 623 { 624 // set env and reboot and break. 625 executeCmd("/sbin/fw_setenv", key, value); 626 log<level::INFO>("Rebooting BMC to pick up new device tree"); 627 // make dbus call to reboot 628 auto bus = sdbusplus::bus::new_default_system(); 629 auto method = bus.new_method_call( 630 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 631 "org.freedesktop.systemd1.Manager", "Reboot"); 632 bus.call_noreply(method); 633 } 634 635 /* 636 * @brief This API checks for env var fitconfig. 637 * If not initialised OR updated as per the current system type, 638 * update this env var and reboot the system. 639 * 640 * @param[in] systemType IM kwd in vpd tells about which system type it is. 641 * */ 642 void setDevTreeEnv(const string& systemType) 643 { 644 string newDeviceTree; 645 646 if (deviceTreeSystemTypeMap.find(systemType) != 647 deviceTreeSystemTypeMap.end()) 648 { 649 newDeviceTree = deviceTreeSystemTypeMap.at(systemType); 650 } 651 652 string readVarValue; 653 bool envVarFound = false; 654 655 vector<string> output = executeCmd("/sbin/fw_printenv"); 656 for (const auto& entry : output) 657 { 658 size_t pos = entry.find("="); 659 string key = entry.substr(0, pos); 660 if (key != "fitconfig") 661 { 662 continue; 663 } 664 665 envVarFound = true; 666 if (pos + 1 < entry.size()) 667 { 668 readVarValue = entry.substr(pos + 1); 669 if (readVarValue.find(newDeviceTree) != string::npos) 670 { 671 // fitconfig is Updated. No action needed 672 break; 673 } 674 } 675 // set env and reboot and break. 676 setEnvAndReboot(key, newDeviceTree); 677 exit(0); 678 } 679 680 // check If env var Not found 681 if (!envVarFound) 682 { 683 setEnvAndReboot("fitconfig", newDeviceTree); 684 } 685 } 686 687 /** 688 * @brief API to call VPD manager to write VPD to EEPROM. 689 * @param[in] Object path. 690 * @param[in] record to be updated. 691 * @param[in] keyword to be updated. 692 * @param[in] keyword data to be updated 693 */ 694 void updateHardware(const string& objectName, const string& recName, 695 const string& kwdName, const Binary& data) 696 { 697 try 698 { 699 auto bus = sdbusplus::bus::new_default(); 700 auto properties = 701 bus.new_method_call(BUSNAME, OBJPATH, IFACE, "WriteKeyword"); 702 properties.append( 703 static_cast<sdbusplus::message::object_path>(objectName)); 704 properties.append(recName); 705 properties.append(kwdName); 706 properties.append(data); 707 bus.call(properties); 708 } 709 catch (const sdbusplus::exception::exception& e) 710 { 711 std::string what = 712 "VPDManager WriteKeyword api failed for inventory path " + 713 objectName; 714 what += " record " + recName; 715 what += " keyword " + kwdName; 716 what += " with bus error = " + std::string(e.what()); 717 718 // map to hold additional data in case of logging pel 719 PelAdditionalData additionalData{}; 720 additionalData.emplace("CALLOUT_INVENTORY_PATH", objectName); 721 additionalData.emplace("DESCRIPTION", what); 722 createPEL(additionalData, PelSeverity::WARNING, errIntfForBusFailure); 723 } 724 } 725 726 /** 727 * @brief API to check if we need to restore system VPD 728 * This functionality is only applicable for IPZ VPD data. 729 * @param[in] vpdMap - IPZ vpd map 730 * @param[in] objectPath - Object path for the FRU 731 * @return EEPROMs with records and keywords updated at standby 732 */ 733 std::vector<RestoredEeproms> restoreSystemVPD(Parsed& vpdMap, 734 const string& objectPath) 735 { 736 // the list of keywords for VSYS record is as per the S0 system. Should be 737 // updated for another type of systems 738 static std::unordered_map<std::string, std::vector<std::string>> svpdKwdMap{ 739 {"VSYS", {"BR", "TM", "SE", "SU", "RB"}}, 740 {"VCEN", {"FC", "SE"}}, 741 {"LXR0", {"LX"}}}; 742 743 // vector to hold all the EEPROMs updated at standby 744 std::vector<RestoredEeproms> updatedEeproms = {}; 745 746 for (const auto& systemRecKwdPair : svpdKwdMap) 747 { 748 auto it = vpdMap.find(systemRecKwdPair.first); 749 750 // check if record is found in map we got by parser 751 if (it != vpdMap.end()) 752 { 753 const auto& kwdListForRecord = systemRecKwdPair.second; 754 for (const auto& keyword : kwdListForRecord) 755 { 756 DbusPropertyMap& kwdValMap = it->second; 757 auto iterator = kwdValMap.find(keyword); 758 759 if (iterator != kwdValMap.end()) 760 { 761 string& kwdValue = iterator->second; 762 763 // check bus data 764 const string& recordName = systemRecKwdPair.first; 765 const string& busValue = readBusProperty( 766 objectPath, ipzVpdInf + recordName, keyword); 767 768 if (busValue.find_first_not_of(' ') != string::npos) 769 { 770 if (kwdValue.find_first_not_of(' ') != string::npos) 771 { 772 // both the data are present, check for mismatch 773 if (busValue != kwdValue) 774 { 775 string errMsg = "VPD data mismatch on cache " 776 "and hardware for record: "; 777 errMsg += (*it).first; 778 errMsg += " and keyword: "; 779 errMsg += keyword; 780 781 // data mismatch 782 PelAdditionalData additionalData; 783 additionalData.emplace("CALLOUT_INVENTORY_PATH", 784 objectPath); 785 786 additionalData.emplace("DESCRIPTION", errMsg); 787 788 createPEL(additionalData, PelSeverity::WARNING, 789 errIntfForInvalidVPD); 790 } 791 } 792 else 793 { 794 // implies hardware data is blank 795 // update the map 796 Binary busData(busValue.begin(), busValue.end()); 797 798 updatedEeproms.push_back(std::make_tuple( 799 objectPath, recordName, keyword, busData)); 800 801 // update the map as well, so that cache data is not 802 // updated as blank while populating VPD map on Dbus 803 // in populateDBus Api 804 kwdValue = busValue; 805 } 806 } 807 else if (kwdValue.find_first_not_of(' ') == string::npos) 808 { 809 string errMsg = "VPD is blank on both cache and " 810 "hardware for record: "; 811 errMsg += (*it).first; 812 errMsg += " and keyword: "; 813 errMsg += keyword; 814 errMsg += ". SSR need to update hardware VPD."; 815 816 // both the data are blanks, log PEL 817 PelAdditionalData additionalData; 818 additionalData.emplace("CALLOUT_INVENTORY_PATH", 819 objectPath); 820 821 additionalData.emplace("DESCRIPTION", errMsg); 822 823 // log PEL TODO: Block IPL 824 createPEL(additionalData, PelSeverity::ERROR, 825 errIntfForBlankSystemVPD); 826 continue; 827 } 828 } 829 } 830 } 831 } 832 833 return updatedEeproms; 834 } 835 836 /** 837 * @brief This checks for is this FRU a processor 838 * And if yes, then checks for is this primary 839 * 840 * @param[in] js- vpd json to get the information about this FRU 841 * @param[in] filePath- FRU vpd 842 * 843 * @return true/false 844 */ 845 bool isThisPrimaryProcessor(nlohmann::json& js, const string& filePath) 846 { 847 bool isProcessor = false; 848 bool isPrimary = false; 849 850 for (const auto& item : js["frus"][filePath]) 851 { 852 if (item.find("extraInterfaces") != item.end()) 853 { 854 for (const auto& eI : item["extraInterfaces"].items()) 855 { 856 if (eI.key().find("Inventory.Item.Cpu") != string::npos) 857 { 858 isProcessor = true; 859 } 860 } 861 } 862 863 if (isProcessor) 864 { 865 string cpuType = item.value("cpuType", ""); 866 if (cpuType == "primary") 867 { 868 isPrimary = true; 869 } 870 } 871 } 872 873 return (isProcessor && isPrimary); 874 } 875 876 /** 877 * @brief This finds DIMM vpd in vpd json and enables them by binding the device 878 * driver 879 * @param[in] js- vpd json to iterate through and take action if it is DIMM 880 */ 881 void doEnableAllDimms(nlohmann::json& js) 882 { 883 // iterate over each fru 884 for (const auto& eachFru : js["frus"].items()) 885 { 886 // skip the driver binding if eeprom already exists 887 if (fs::exists(eachFru.key())) 888 { 889 continue; 890 } 891 892 for (const auto& eachInventory : eachFru.value()) 893 { 894 if (eachInventory.find("extraInterfaces") != eachInventory.end()) 895 { 896 for (const auto& eI : eachInventory["extraInterfaces"].items()) 897 { 898 if (eI.key().find("Inventory.Item.Dimm") != string::npos) 899 { 900 string dimmVpd = eachFru.key(); 901 // fetch it from 902 // "/sys/bus/i2c/drivers/at24/414-0050/eeprom" 903 904 regex matchPatern("([0-9]+-[0-9]{4})"); 905 smatch matchFound; 906 if (regex_search(dimmVpd, matchFound, matchPatern)) 907 { 908 vector<string> i2cReg; 909 boost::split(i2cReg, matchFound.str(0), 910 boost::is_any_of("-")); 911 912 // remove 0s from begining 913 const regex pattern("^0+(?!$)"); 914 for (auto& i : i2cReg) 915 { 916 i = regex_replace(i, pattern, ""); 917 } 918 919 if (i2cReg.size() == 2) 920 { 921 // echo 24c32 0x50 > 922 // /sys/bus/i2c/devices/i2c-16/new_device 923 string cmnd = "echo 24c32 0x" + i2cReg[1] + 924 " > /sys/bus/i2c/devices/i2c-" + 925 i2cReg[0] + "/new_device"; 926 927 executeCmd(cmnd); 928 } 929 } 930 } 931 } 932 } 933 } 934 } 935 } 936 937 /** 938 * @brief Populate Dbus. 939 * This method invokes all the populateInterface functions 940 * and notifies PIM about dbus object. 941 * @param[in] vpdMap - Either IPZ vpd map or Keyword vpd map based on the 942 * input. 943 * @param[in] js - Inventory json object 944 * @param[in] filePath - Path of the vpd file 945 * @param[in] preIntrStr - Interface string 946 */ 947 template <typename T> 948 static void populateDbus(T& vpdMap, nlohmann::json& js, const string& filePath) 949 { 950 inventory::InterfaceMap interfaces; 951 inventory::ObjectMap objects; 952 inventory::PropertyMap prop; 953 954 // map to hold all the keywords whose value has been changed at standby 955 vector<RestoredEeproms> updatedEeproms = {}; 956 957 bool isSystemVpd = (filePath == systemVpdFilePath); 958 if constexpr (is_same<T, Parsed>::value) 959 { 960 if (isSystemVpd) 961 { 962 std::vector<std::string> interfaces = {motherBoardInterface}; 963 // call mapper to check for object path creation 964 MapperResponse subTree = 965 getObjectSubtreeForInterfaces(pimPath, 0, interfaces); 966 string mboardPath = 967 js["frus"][filePath].at(0).value("inventoryPath", ""); 968 969 // Attempt system VPD restore if we have a motherboard 970 // object in the inventory. 971 if ((subTree.size() != 0) && 972 (subTree.find(pimPath + mboardPath) != subTree.end())) 973 { 974 updatedEeproms = restoreSystemVPD(vpdMap, mboardPath); 975 } 976 else 977 { 978 log<level::ERR>("No object path found"); 979 } 980 } 981 else 982 { 983 // check if it is processor vpd. 984 auto isPrimaryCpu = isThisPrimaryProcessor(js, filePath); 985 986 if (isPrimaryCpu) 987 { 988 auto ddVersion = getKwVal(vpdMap, "CRP0", "DD"); 989 990 auto chipVersion = atoi(ddVersion.substr(1, 2).c_str()); 991 992 if (chipVersion >= 2) 993 { 994 doEnableAllDimms(js); 995 } 996 } 997 } 998 } 999 1000 for (const auto& item : js["frus"][filePath]) 1001 { 1002 const auto& objectPath = item["inventoryPath"]; 1003 sdbusplus::message::object_path object(objectPath); 1004 1005 if (isSystemVpd) 1006 { 1007 // Populate one time properties for the system VPD and its sub-frus. 1008 // For the remaining FRUs, this will get handled as a part of 1009 // priming the inventory. 1010 setOneTimeProperties(objectPath, interfaces); 1011 } 1012 1013 // Populate the VPD keywords and the common interfaces only if we 1014 // are asked to inherit that data from the VPD, else only add the 1015 // extraInterfaces. 1016 if (item.value("inherit", true)) 1017 { 1018 if constexpr (is_same<T, Parsed>::value) 1019 { 1020 // Each record in the VPD becomes an interface and all 1021 // keyword within the record are properties under that 1022 // interface. 1023 for (const auto& record : vpdMap) 1024 { 1025 populateFruSpecificInterfaces( 1026 record.second, ipzVpdInf + record.first, interfaces); 1027 } 1028 } 1029 else if constexpr (is_same<T, KeywordVpdMap>::value) 1030 { 1031 populateFruSpecificInterfaces(vpdMap, kwdVpdInf, interfaces); 1032 } 1033 if (js.find("commonInterfaces") != js.end()) 1034 { 1035 populateInterfaces(js["commonInterfaces"], interfaces, vpdMap, 1036 isSystemVpd); 1037 } 1038 } 1039 else 1040 { 1041 // Check if we have been asked to inherit specific record(s) 1042 if constexpr (is_same<T, Parsed>::value) 1043 { 1044 if (item.find("copyRecords") != item.end()) 1045 { 1046 for (const auto& record : item["copyRecords"]) 1047 { 1048 const string& recordName = record; 1049 if (vpdMap.find(recordName) != vpdMap.end()) 1050 { 1051 populateFruSpecificInterfaces( 1052 vpdMap.at(recordName), ipzVpdInf + recordName, 1053 interfaces); 1054 } 1055 } 1056 } 1057 } 1058 } 1059 if (item.value("inheritEI", true)) 1060 { 1061 // Populate interfaces and properties that are common to every FRU 1062 // and additional interface that might be defined on a per-FRU 1063 // basis. 1064 if (item.find("extraInterfaces") != item.end()) 1065 { 1066 populateInterfaces(item["extraInterfaces"], interfaces, vpdMap, 1067 isSystemVpd); 1068 } 1069 } 1070 objects.emplace(move(object), move(interfaces)); 1071 } 1072 1073 if (isSystemVpd) 1074 { 1075 string systemJsonName{}; 1076 if constexpr (is_same<T, Parsed>::value) 1077 { 1078 // pick the right system json 1079 systemJsonName = getSystemsJson(vpdMap); 1080 } 1081 1082 fs::path target = systemJsonName; 1083 fs::path link = INVENTORY_JSON_SYM_LINK; 1084 1085 // Create the directory for hosting the symlink 1086 fs::create_directories(VPD_FILES_PATH); 1087 // unlink the symlink previously created (if any) 1088 remove(INVENTORY_JSON_SYM_LINK); 1089 // create a new symlink based on the system 1090 fs::create_symlink(target, link); 1091 1092 // Reloading the json 1093 ifstream inventoryJson(link); 1094 auto js = json::parse(inventoryJson); 1095 inventoryJson.close(); 1096 1097 inventory::ObjectMap primeObject = primeInventory(js, vpdMap); 1098 objects.insert(primeObject.begin(), primeObject.end()); 1099 1100 // if system VPD has been restored at standby, update the EEPROM 1101 for (const auto& item : updatedEeproms) 1102 { 1103 updateHardware(get<0>(item), get<1>(item), get<2>(item), 1104 get<3>(item)); 1105 } 1106 1107 // set the U-boot environment variable for device-tree 1108 if constexpr (is_same<T, Parsed>::value) 1109 { 1110 const string imKeyword = getIM(vpdMap); 1111 const string hwKeyword = getHW(vpdMap); 1112 string systemType = imKeyword; 1113 1114 // check If system has constraint then append HW version to it. 1115 ifstream sysJson(SYSTEM_JSON); 1116 if (!sysJson) 1117 { 1118 throw((VpdJsonException("Failed to access Json path", 1119 SYSTEM_JSON))); 1120 } 1121 1122 try 1123 { 1124 auto systemJson = json::parse(sysJson); 1125 if (systemJson["system"].find(imKeyword) != 1126 systemJson["system"].end()) 1127 { 1128 if (systemJson["system"][imKeyword].find("constraint") != 1129 systemJson["system"][imKeyword].end()) 1130 { 1131 systemType += "_" + hwKeyword; 1132 } 1133 } 1134 } 1135 catch (const json::parse_error& ex) 1136 { 1137 throw((VpdJsonException("System Json parsing failed", 1138 SYSTEM_JSON))); 1139 } 1140 1141 setDevTreeEnv(systemType); 1142 } 1143 } 1144 1145 // Notify PIM 1146 common::utility::callPIM(move(objects)); 1147 } 1148 1149 int main(int argc, char** argv) 1150 { 1151 int rc = 0; 1152 json js{}; 1153 Binary vpdVector{}; 1154 string file{}; 1155 // map to hold additional data in case of logging pel 1156 PelAdditionalData additionalData{}; 1157 1158 // this is needed to hold base fru inventory path in case there is ECC or 1159 // vpd exception while parsing the file 1160 std::string baseFruInventoryPath = {}; 1161 1162 // severity for PEL 1163 PelSeverity pelSeverity = PelSeverity::WARNING; 1164 1165 try 1166 { 1167 App app{"ibm-read-vpd - App to read IPZ format VPD, parse it and store " 1168 "in DBUS"}; 1169 1170 app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)") 1171 ->required(); 1172 1173 CLI11_PARSE(app, argc, argv); 1174 1175 cout << "Parser launched with file: " << file << "\n"; 1176 1177 // PEL severity should be ERROR in case of any system VPD failure 1178 if (file == systemVpdFilePath) 1179 { 1180 pelSeverity = PelSeverity::ERROR; 1181 } 1182 1183 auto jsonToParse = INVENTORY_JSON_DEFAULT; 1184 1185 // If the symlink exists, it means it has been setup for us, switch the 1186 // path 1187 if (fs::exists(INVENTORY_JSON_SYM_LINK)) 1188 { 1189 jsonToParse = INVENTORY_JSON_SYM_LINK; 1190 } 1191 1192 // Make sure that the file path we get is for a supported EEPROM 1193 ifstream inventoryJson(jsonToParse); 1194 if (!inventoryJson) 1195 { 1196 throw(VpdJsonException("Failed to access Json path", jsonToParse)); 1197 } 1198 1199 try 1200 { 1201 js = json::parse(inventoryJson); 1202 } 1203 catch (const json::parse_error& ex) 1204 { 1205 throw(VpdJsonException("Json parsing failed", jsonToParse)); 1206 } 1207 1208 // Do we have the mandatory "frus" section? 1209 if (js.find("frus") == js.end()) 1210 { 1211 throw(VpdJsonException("FRUs section not found in JSON", 1212 jsonToParse)); 1213 } 1214 1215 // Check if it's a udev path - patterned as(/ahb/ahb:apb/ahb:apb:bus@) 1216 if (file.find("/ahb:apb") != string::npos) 1217 { 1218 // Translate udev path to a generic /sys/bus/.. file path. 1219 udevToGenericPath(file); 1220 cout << "Path after translation: " << file << "\n"; 1221 1222 if ((js["frus"].find(file) != js["frus"].end()) && 1223 (file == systemVpdFilePath)) 1224 { 1225 return 0; 1226 } 1227 } 1228 1229 if (file.empty()) 1230 { 1231 cerr << "The EEPROM path <" << file << "> is not valid."; 1232 return 0; 1233 } 1234 if (js["frus"].find(file) == js["frus"].end()) 1235 { 1236 cout << "Device path not in JSON, ignoring" << endl; 1237 return 0; 1238 } 1239 1240 if (!fs::exists(file)) 1241 { 1242 cout << "Device path: " << file 1243 << " does not exist. Spurious udev event? Exiting." << endl; 1244 return 0; 1245 } 1246 1247 baseFruInventoryPath = js["frus"][file][0]["inventoryPath"]; 1248 // Check if we can read the VPD file based on the power state 1249 if (js["frus"][file].at(0).value("powerOffOnly", false)) 1250 { 1251 if ("xyz.openbmc_project.State.Chassis.PowerState.On" == 1252 getPowerState()) 1253 { 1254 cout << "This VPD cannot be read when power is ON" << endl; 1255 return 0; 1256 } 1257 } 1258 1259 try 1260 { 1261 vpdVector = getVpdDataInVector(js, file); 1262 ParserInterface* parser = ParserFactory::getParser(vpdVector); 1263 variant<KeywordVpdMap, Store> parseResult; 1264 parseResult = parser->parse(); 1265 1266 if (auto pVal = get_if<Store>(&parseResult)) 1267 { 1268 populateDbus(pVal->getVpdMap(), js, file); 1269 } 1270 else if (auto pVal = get_if<KeywordVpdMap>(&parseResult)) 1271 { 1272 populateDbus(*pVal, js, file); 1273 } 1274 1275 // release the parser object 1276 ParserFactory::freeParser(parser); 1277 } 1278 catch (const exception& e) 1279 { 1280 postFailAction(js, file); 1281 throw; 1282 } 1283 } 1284 catch (const VpdJsonException& ex) 1285 { 1286 additionalData.emplace("JSON_PATH", ex.getJsonPath()); 1287 additionalData.emplace("DESCRIPTION", ex.what()); 1288 createPEL(additionalData, pelSeverity, errIntfForJsonFailure); 1289 1290 cerr << ex.what() << "\n"; 1291 rc = -1; 1292 } 1293 catch (const VpdEccException& ex) 1294 { 1295 additionalData.emplace("DESCRIPTION", "ECC check failed"); 1296 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1297 INVENTORY_PATH + baseFruInventoryPath); 1298 createPEL(additionalData, pelSeverity, errIntfForEccCheckFail); 1299 dumpBadVpd(file, vpdVector); 1300 cerr << ex.what() << "\n"; 1301 rc = -1; 1302 } 1303 catch (const VpdDataException& ex) 1304 { 1305 additionalData.emplace("DESCRIPTION", "Invalid VPD data"); 1306 additionalData.emplace("CALLOUT_INVENTORY_PATH", 1307 INVENTORY_PATH + baseFruInventoryPath); 1308 createPEL(additionalData, pelSeverity, errIntfForInvalidVPD); 1309 dumpBadVpd(file, vpdVector); 1310 cerr << ex.what() << "\n"; 1311 rc = -1; 1312 } 1313 catch (const exception& e) 1314 { 1315 dumpBadVpd(file, vpdVector); 1316 cerr << e.what() << "\n"; 1317 rc = -1; 1318 } 1319 1320 return rc; 1321 }