1 #include "config.h" 2 3 #include "worker.hpp" 4 5 #include "backup_restore.hpp" 6 #include "configuration.hpp" 7 #include "constants.hpp" 8 #include "event_logger.hpp" 9 #include "exceptions.hpp" 10 #include "logger.hpp" 11 #include "parser.hpp" 12 #include "parser_factory.hpp" 13 #include "parser_interface.hpp" 14 15 #include <utility/dbus_utility.hpp> 16 #include <utility/json_utility.hpp> 17 #include <utility/vpd_specific_utility.hpp> 18 19 #include <filesystem> 20 #include <fstream> 21 #include <future> 22 #include <typeindex> 23 #include <unordered_set> 24 25 namespace vpd 26 { 27 28 Worker::Worker(std::string pathToConfigJson, uint8_t i_maxThreadCount) : 29 m_configJsonPath(pathToConfigJson), m_semaphore(i_maxThreadCount) 30 { 31 // Implies the processing is based on some config JSON 32 if (!m_configJsonPath.empty()) 33 { 34 // Check if symlink is already there to confirm fresh boot/factory 35 // reset. 36 if (std::filesystem::exists(INVENTORY_JSON_SYM_LINK)) 37 { 38 logging::logMessage("Sym Link already present"); 39 m_configJsonPath = INVENTORY_JSON_SYM_LINK; 40 m_isSymlinkPresent = true; 41 } 42 43 try 44 { 45 m_parsedJson = jsonUtility::getParsedJson(m_configJsonPath); 46 47 // check for mandatory fields at this point itself. 48 if (!m_parsedJson.contains("frus")) 49 { 50 throw std::runtime_error("Mandatory tag(s) missing from JSON"); 51 } 52 } 53 catch (const std::exception& ex) 54 { 55 throw(JsonException(ex.what(), m_configJsonPath)); 56 } 57 } 58 else 59 { 60 logging::logMessage("Processing in not based on any config JSON"); 61 } 62 } 63 64 static std::string readFitConfigValue() 65 { 66 std::vector<std::string> output = 67 commonUtility::executeCmd("/sbin/fw_printenv"); 68 std::string fitConfigValue; 69 70 for (const auto& entry : output) 71 { 72 auto pos = entry.find("="); 73 auto key = entry.substr(0, pos); 74 if (key != "fitconfig") 75 { 76 continue; 77 } 78 79 if (pos + 1 < entry.size()) 80 { 81 fitConfigValue = entry.substr(pos + 1); 82 } 83 } 84 85 return fitConfigValue; 86 } 87 88 bool Worker::isSystemVPDOnDBus() const 89 { 90 const std::string& mboardPath = 91 m_parsedJson["frus"][SYSTEM_VPD_FILE_PATH].at(0).value( 92 "inventoryPath", ""); 93 94 if (mboardPath.empty()) 95 { 96 throw JsonException("System vpd file path missing in JSON", 97 INVENTORY_JSON_SYM_LINK); 98 } 99 100 std::array<const char*, 1> interfaces = { 101 "xyz.openbmc_project.Inventory.Item.Board.Motherboard"}; 102 103 const types::MapperGetObject& objectMap = 104 dbusUtility::getObjectMap(mboardPath, interfaces); 105 106 if (objectMap.empty()) 107 { 108 return false; 109 } 110 return true; 111 } 112 113 std::string Worker::getIMValue(const types::IPZVpdMap& parsedVpd) const 114 { 115 if (parsedVpd.empty()) 116 { 117 throw std::runtime_error("Empty VPD map. Can't Extract IM value"); 118 } 119 120 const auto& itrToVSBP = parsedVpd.find("VSBP"); 121 if (itrToVSBP == parsedVpd.end()) 122 { 123 throw DataException("VSBP record missing."); 124 } 125 126 const auto& itrToIM = (itrToVSBP->second).find("IM"); 127 if (itrToIM == (itrToVSBP->second).end()) 128 { 129 throw DataException("IM keyword missing."); 130 } 131 132 types::BinaryVector imVal; 133 std::copy(itrToIM->second.begin(), itrToIM->second.end(), 134 back_inserter(imVal)); 135 136 std::ostringstream imData; 137 for (auto& aByte : imVal) 138 { 139 imData << std::setw(2) << std::setfill('0') << std::hex 140 << static_cast<int>(aByte); 141 } 142 143 return imData.str(); 144 } 145 146 std::string Worker::getHWVersion(const types::IPZVpdMap& parsedVpd) const 147 { 148 if (parsedVpd.empty()) 149 { 150 throw std::runtime_error("Empty VPD map. Can't Extract HW value"); 151 } 152 153 const auto& itrToVINI = parsedVpd.find("VINI"); 154 if (itrToVINI == parsedVpd.end()) 155 { 156 throw DataException("VINI record missing."); 157 } 158 159 const auto& itrToHW = (itrToVINI->second).find("HW"); 160 if (itrToHW == (itrToVINI->second).end()) 161 { 162 throw DataException("HW keyword missing."); 163 } 164 165 types::BinaryVector hwVal; 166 std::copy(itrToHW->second.begin(), itrToHW->second.end(), 167 back_inserter(hwVal)); 168 169 // The planar pass only comes from the LSB of the HW keyword, 170 // where as the MSB is used for other purposes such as signifying clock 171 // termination. 172 hwVal[0] = 0x00; 173 174 std::ostringstream hwString; 175 for (auto& aByte : hwVal) 176 { 177 hwString << std::setw(2) << std::setfill('0') << std::hex 178 << static_cast<int>(aByte); 179 } 180 181 return hwString.str(); 182 } 183 184 void Worker::fillVPDMap(const std::string& vpdFilePath, 185 types::VPDMapVariant& vpdMap) 186 { 187 logging::logMessage(std::string("Parsing file = ") + vpdFilePath); 188 189 if (vpdFilePath.empty()) 190 { 191 throw std::runtime_error("Invalid file path passed to fillVPDMap API."); 192 } 193 194 if (!std::filesystem::exists(vpdFilePath)) 195 { 196 throw std::runtime_error("Can't Find physical file"); 197 } 198 199 std::shared_ptr<Parser> vpdParser = 200 std::make_shared<Parser>(vpdFilePath, m_parsedJson); 201 vpdMap = vpdParser->parse(); 202 } 203 204 void Worker::getSystemJson(std::string& systemJson, 205 const types::VPDMapVariant& parsedVpdMap) 206 { 207 if (auto pVal = std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 208 { 209 std::string hwKWdValue = getHWVersion(*pVal); 210 if (hwKWdValue.empty()) 211 { 212 throw DataException("HW value fetched is empty."); 213 } 214 215 const std::string& imKwdValue = getIMValue(*pVal); 216 if (imKwdValue.empty()) 217 { 218 throw DataException("IM value fetched is empty."); 219 } 220 221 auto itrToIM = config::systemType.find(imKwdValue); 222 if (itrToIM == config::systemType.end()) 223 { 224 throw DataException("IM keyword does not map to any system type"); 225 } 226 227 const types::HWVerList hwVersionList = itrToIM->second.second; 228 if (!hwVersionList.empty()) 229 { 230 transform(hwKWdValue.begin(), hwKWdValue.end(), hwKWdValue.begin(), 231 ::toupper); 232 233 auto itrToHW = 234 std::find_if(hwVersionList.begin(), hwVersionList.end(), 235 [&hwKWdValue](const auto& aPair) { 236 return aPair.first == hwKWdValue; 237 }); 238 239 if (itrToHW != hwVersionList.end()) 240 { 241 if (!(*itrToHW).second.empty()) 242 { 243 systemJson += (*itrToIM).first + "_" + (*itrToHW).second + 244 ".json"; 245 } 246 else 247 { 248 systemJson += (*itrToIM).first + ".json"; 249 } 250 return; 251 } 252 } 253 systemJson += itrToIM->second.first + ".json"; 254 return; 255 } 256 257 throw DataException( 258 "Invalid VPD type returned from Parser. Can't get system JSON."); 259 } 260 261 static void setEnvAndReboot(const std::string& key, const std::string& value) 262 { 263 // set env and reboot and break. 264 commonUtility::executeCmd("/sbin/fw_setenv", key, value); 265 logging::logMessage("Rebooting BMC to pick up new device tree"); 266 267 // make dbus call to reboot 268 auto bus = sdbusplus::bus::new_default_system(); 269 auto method = bus.new_method_call( 270 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 271 "org.freedesktop.systemd1.Manager", "Reboot"); 272 bus.call_noreply(method); 273 } 274 275 void Worker::setJsonSymbolicLink(const std::string& i_systemJson) 276 { 277 std::error_code l_ec; 278 l_ec.clear(); 279 280 // Check if symlink file path exists and if the JSON at this location is a 281 // symlink. 282 if (m_isSymlinkPresent && 283 std::filesystem::is_symlink(INVENTORY_JSON_SYM_LINK, l_ec)) 284 { // Don't care about exception in "is_symlink". Will continue with creation 285 // of symlink. 286 287 const auto& l_symlinkFilePth = 288 std::filesystem::read_symlink(INVENTORY_JSON_SYM_LINK, l_ec); 289 290 if (l_ec) 291 { 292 logging::logMessage( 293 "Can't read existing symlink. Error =" + l_ec.message() + 294 "Trying removal of symlink and creation of new symlink."); 295 } 296 297 // If currently set JSON is the required one. No further processing 298 // required. 299 if (i_systemJson == l_symlinkFilePth) 300 { 301 // Correct symlink already set. 302 return; 303 } 304 305 if (!std::filesystem::remove(INVENTORY_JSON_SYM_LINK, l_ec)) 306 { 307 // No point going further. If removal fails for existing symlink, 308 // create will anyways throw. 309 throw std::runtime_error( 310 "Removal of symlink failed with Error = " + l_ec.message() + 311 ". Can't proceed with create_symlink."); 312 } 313 } 314 315 if (!std::filesystem::exists(VPD_SYMLIMK_PATH, l_ec)) 316 { 317 if (l_ec) 318 { 319 throw std::runtime_error( 320 "File system call to exist failed with error = " + 321 l_ec.message()); 322 } 323 324 // implies it is a fresh boot/factory reset. 325 // Create the directory for hosting the symlink 326 if (!std::filesystem::create_directories(VPD_SYMLIMK_PATH, l_ec)) 327 { 328 if (l_ec) 329 { 330 throw std::runtime_error( 331 "File system call to create directory failed with error = " + 332 l_ec.message()); 333 } 334 } 335 } 336 337 // create a new symlink based on the system 338 std::filesystem::create_symlink(i_systemJson, INVENTORY_JSON_SYM_LINK, 339 l_ec); 340 341 if (l_ec) 342 { 343 throw std::runtime_error( 344 "create_symlink system call failed with error: " + l_ec.message()); 345 } 346 347 // If the flow is at this point implies the symlink was not present there. 348 // Considering this as factory reset. 349 m_isFactoryResetDone = true; 350 } 351 352 void Worker::setDeviceTreeAndJson() 353 { 354 // JSON is madatory for processing of this API. 355 if (m_parsedJson.empty()) 356 { 357 throw JsonException("System config JSON is empty", m_configJsonPath); 358 } 359 360 types::VPDMapVariant parsedVpdMap; 361 fillVPDMap(SYSTEM_VPD_FILE_PATH, parsedVpdMap); 362 363 // Implies it is default JSON. 364 std::string systemJson{JSON_ABSOLUTE_PATH_PREFIX}; 365 366 // ToDo: Need to check if INVENTORY_JSON_SYM_LINK pointing to correct system 367 // This is required to support movement from rainier to Blue Ridge on the 368 // fly. 369 370 getSystemJson(systemJson, parsedVpdMap); 371 372 if (!systemJson.compare(JSON_ABSOLUTE_PATH_PREFIX)) 373 { 374 throw DataException( 375 "No system JSON found corresponding to IM read from VPD."); 376 } 377 378 // re-parse the JSON once appropriate JSON has been selected. 379 m_parsedJson = jsonUtility::getParsedJson(systemJson); 380 381 if (m_parsedJson.empty()) 382 { 383 throw(JsonException("Json parsing failed", systemJson)); 384 } 385 386 std::string devTreeFromJson; 387 if (m_parsedJson.contains("devTree")) 388 { 389 devTreeFromJson = m_parsedJson["devTree"]; 390 391 if (devTreeFromJson.empty()) 392 { 393 EventLogger::createSyncPel( 394 types::ErrorType::JsonFailure, types::SeverityType::Error, 395 __FILE__, __FUNCTION__, 0, 396 "Mandatory value for device tree missing from JSON[" + 397 systemJson + "]", 398 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 399 } 400 } 401 402 auto fitConfigVal = readFitConfigValue(); 403 404 if (devTreeFromJson.empty() || 405 fitConfigVal.find(devTreeFromJson) != std::string::npos) 406 { // Skipping setting device tree as either devtree info is missing from 407 // Json or it is rightly set. 408 409 setJsonSymbolicLink(systemJson); 410 411 if (isSystemVPDOnDBus() && 412 jsonUtility::isBackupAndRestoreRequired(m_parsedJson)) 413 { 414 performBackupAndRestore(parsedVpdMap); 415 } 416 417 // proceed to publish system VPD. 418 publishSystemVPD(parsedVpdMap); 419 return; 420 } 421 422 setEnvAndReboot("fitconfig", devTreeFromJson); 423 exit(EXIT_SUCCESS); 424 } 425 426 void Worker::populateIPZVPDpropertyMap( 427 types::InterfaceMap& interfacePropMap, 428 const types::IPZKwdValueMap& keyordValueMap, 429 const std::string& interfaceName) 430 { 431 types::PropertyMap propertyValueMap; 432 for (const auto& kwdVal : keyordValueMap) 433 { 434 auto kwd = kwdVal.first; 435 436 if (kwd[0] == '#') 437 { 438 kwd = std::string("PD_") + kwd[1]; 439 } 440 else if (isdigit(kwd[0])) 441 { 442 kwd = std::string("N_") + kwd; 443 } 444 445 types::BinaryVector value(kwdVal.second.begin(), kwdVal.second.end()); 446 propertyValueMap.emplace(move(kwd), move(value)); 447 } 448 449 if (!propertyValueMap.empty()) 450 { 451 interfacePropMap.emplace(interfaceName, propertyValueMap); 452 } 453 } 454 455 void Worker::populateKwdVPDpropertyMap(const types::KeywordVpdMap& keyordVPDMap, 456 types::InterfaceMap& interfaceMap) 457 { 458 for (const auto& kwdValMap : keyordVPDMap) 459 { 460 types::PropertyMap propertyValueMap; 461 auto kwd = kwdValMap.first; 462 463 if (kwd[0] == '#') 464 { 465 kwd = std::string("PD_") + kwd[1]; 466 } 467 else if (isdigit(kwd[0])) 468 { 469 kwd = std::string("N_") + kwd; 470 } 471 472 if (auto keywordValue = get_if<types::BinaryVector>(&kwdValMap.second)) 473 { 474 types::BinaryVector value((*keywordValue).begin(), 475 (*keywordValue).end()); 476 propertyValueMap.emplace(move(kwd), move(value)); 477 } 478 else if (auto keywordValue = get_if<std::string>(&kwdValMap.second)) 479 { 480 types::BinaryVector value((*keywordValue).begin(), 481 (*keywordValue).end()); 482 propertyValueMap.emplace(move(kwd), move(value)); 483 } 484 else if (auto keywordValue = get_if<size_t>(&kwdValMap.second)) 485 { 486 if (kwd == "MemorySizeInKB") 487 { 488 types::PropertyMap memProp; 489 memProp.emplace(move(kwd), ((*keywordValue))); 490 interfaceMap.emplace("xyz.openbmc_project.Inventory.Item.Dimm", 491 move(memProp)); 492 continue; 493 } 494 else 495 { 496 logging::logMessage( 497 "Unknown Keyword =" + kwd + " found in keyword VPD map"); 498 continue; 499 } 500 } 501 else 502 { 503 logging::logMessage( 504 "Unknown variant type found in keyword VPD map."); 505 continue; 506 } 507 508 if (!propertyValueMap.empty()) 509 { 510 vpdSpecificUtility::insertOrMerge( 511 interfaceMap, constants::kwdVpdInf, move(propertyValueMap)); 512 } 513 } 514 } 515 516 void Worker::populateInterfaces(const nlohmann::json& interfaceJson, 517 types::InterfaceMap& interfaceMap, 518 const types::VPDMapVariant& parsedVpdMap) 519 { 520 for (const auto& interfacesPropPair : interfaceJson.items()) 521 { 522 const std::string& interface = interfacesPropPair.key(); 523 types::PropertyMap propertyMap; 524 525 for (const auto& propValuePair : interfacesPropPair.value().items()) 526 { 527 const std::string property = propValuePair.key(); 528 529 if (propValuePair.value().is_boolean()) 530 { 531 propertyMap.emplace(property, 532 propValuePair.value().get<bool>()); 533 } 534 else if (propValuePair.value().is_string()) 535 { 536 if (property.compare("LocationCode") == 0 && 537 interface.compare("com.ibm.ipzvpd.Location") == 0) 538 { 539 std::string value = 540 vpdSpecificUtility::getExpandedLocationCode( 541 propValuePair.value().get<std::string>(), 542 parsedVpdMap); 543 propertyMap.emplace(property, value); 544 545 auto l_locCodeProperty = propertyMap; 546 vpdSpecificUtility::insertOrMerge( 547 interfaceMap, 548 std::string(constants::xyzLocationCodeInf), 549 move(l_locCodeProperty)); 550 } 551 else 552 { 553 propertyMap.emplace( 554 property, propValuePair.value().get<std::string>()); 555 } 556 } 557 else if (propValuePair.value().is_array()) 558 { 559 try 560 { 561 propertyMap.emplace( 562 property, 563 propValuePair.value().get<types::BinaryVector>()); 564 } 565 catch (const nlohmann::detail::type_error& e) 566 { 567 std::cerr << "Type exception: " << e.what() << "\n"; 568 } 569 } 570 else if (propValuePair.value().is_number()) 571 { 572 // For now assume the value is a size_t. In the future it would 573 // be nice to come up with a way to get the type from the JSON. 574 propertyMap.emplace(property, 575 propValuePair.value().get<size_t>()); 576 } 577 else if (propValuePair.value().is_object()) 578 { 579 const std::string& record = 580 propValuePair.value().value("recordName", ""); 581 const std::string& keyword = 582 propValuePair.value().value("keywordName", ""); 583 const std::string& encoding = 584 propValuePair.value().value("encoding", ""); 585 586 if (auto ipzVpdMap = 587 std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 588 { 589 if (!record.empty() && !keyword.empty() && 590 (*ipzVpdMap).count(record) && 591 (*ipzVpdMap).at(record).count(keyword)) 592 { 593 auto encoded = vpdSpecificUtility::encodeKeyword( 594 ((*ipzVpdMap).at(record).at(keyword)), encoding); 595 propertyMap.emplace(property, encoded); 596 } 597 } 598 else if (auto kwdVpdMap = 599 std::get_if<types::KeywordVpdMap>(&parsedVpdMap)) 600 { 601 if (!keyword.empty() && (*kwdVpdMap).count(keyword)) 602 { 603 if (auto kwValue = std::get_if<types::BinaryVector>( 604 &(*kwdVpdMap).at(keyword))) 605 { 606 auto encodedValue = 607 vpdSpecificUtility::encodeKeyword( 608 std::string((*kwValue).begin(), 609 (*kwValue).end()), 610 encoding); 611 612 propertyMap.emplace(property, encodedValue); 613 } 614 else if (auto kwValue = std::get_if<std::string>( 615 &(*kwdVpdMap).at(keyword))) 616 { 617 auto encodedValue = 618 vpdSpecificUtility::encodeKeyword( 619 std::string((*kwValue).begin(), 620 (*kwValue).end()), 621 encoding); 622 623 propertyMap.emplace(property, encodedValue); 624 } 625 else if (auto uintValue = std::get_if<size_t>( 626 &(*kwdVpdMap).at(keyword))) 627 { 628 propertyMap.emplace(property, *uintValue); 629 } 630 else 631 { 632 logging::logMessage( 633 "Unknown keyword found, Keywrod = " + keyword); 634 } 635 } 636 } 637 } 638 } 639 vpdSpecificUtility::insertOrMerge(interfaceMap, interface, 640 move(propertyMap)); 641 } 642 } 643 644 bool Worker::isCPUIOGoodOnly(const std::string& i_pgKeyword) 645 { 646 const unsigned char l_io[] = { 647 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 648 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF}; 649 650 // EQ0 index (in PG keyword) starts at 97 (with offset starting from 0). 651 // Each EQ carries 3 bytes of data. Totally there are 8 EQs. If all EQs' 652 // value equals 0xE7F9FF, then the cpu has no good cores and its treated as 653 // IO. 654 if (memcmp(l_io, i_pgKeyword.data() + constants::INDEX_OF_EQ0_IN_PG, 655 constants::SIZE_OF_8EQ_IN_PG) == 0) 656 { 657 return true; 658 } 659 660 // The CPU is not an IO 661 return false; 662 } 663 664 bool Worker::primeInventory(const std::string& i_vpdFilePath) 665 { 666 if (i_vpdFilePath.empty()) 667 { 668 logging::logMessage("Empty VPD file path given"); 669 return false; 670 } 671 672 if (m_parsedJson.empty()) 673 { 674 logging::logMessage("Empty JSON detected for " + i_vpdFilePath); 675 return false; 676 } 677 else if (!m_parsedJson["frus"].contains(i_vpdFilePath)) 678 { 679 logging::logMessage("File " + i_vpdFilePath + 680 ", is not found in the system config JSON file."); 681 return false; 682 } 683 684 types::ObjectMap l_objectInterfaceMap; 685 for (const auto& l_Fru : m_parsedJson["frus"][i_vpdFilePath]) 686 { 687 types::InterfaceMap l_interfaces; 688 sdbusplus::message::object_path l_fruObjectPath(l_Fru["inventoryPath"]); 689 690 if (l_Fru.contains("ccin")) 691 { 692 continue; 693 } 694 695 if (l_Fru.contains("noprime") && l_Fru.value("noprime", false)) 696 { 697 continue; 698 } 699 700 // Reset data under PIM for this FRU only if the FRU is not synthesized 701 // and we handle it's Present property. 702 if (isPresentPropertyHandlingRequired(l_Fru)) 703 { 704 // Clear data under PIM if already exists. 705 vpdSpecificUtility::resetDataUnderPIM( 706 std::string(l_Fru["inventoryPath"]), l_interfaces); 707 } 708 709 // Add extra interfaces mentioned in the Json config file 710 if (l_Fru.contains("extraInterfaces")) 711 { 712 populateInterfaces(l_Fru["extraInterfaces"], l_interfaces, 713 std::monostate{}); 714 } 715 716 types::PropertyMap l_propertyValueMap; 717 718 // Update Present property for this FRU only if we handle Present 719 // property for the FRU. 720 if (isPresentPropertyHandlingRequired(l_Fru)) 721 { 722 l_propertyValueMap.emplace("Present", false); 723 724 // TODO: Present based on file will be taken care in future. 725 // By default present is set to false for FRU at the time of 726 // priming. Once collection goes through, it will be set to true in 727 // that flow. 728 /*if (std::filesystem::exists(i_vpdFilePath)) 729 { 730 l_propertyValueMap["Present"] = true; 731 }*/ 732 } 733 734 vpdSpecificUtility::insertOrMerge(l_interfaces, 735 "xyz.openbmc_project.Inventory.Item", 736 move(l_propertyValueMap)); 737 738 if (l_Fru.value("inherit", true) && 739 m_parsedJson.contains("commonInterfaces")) 740 { 741 populateInterfaces(m_parsedJson["commonInterfaces"], l_interfaces, 742 std::monostate{}); 743 } 744 745 processFunctionalProperty(l_Fru["inventoryPath"], l_interfaces); 746 processEnabledProperty(l_Fru["inventoryPath"], l_interfaces); 747 748 // Emplace the default state of FRU VPD collection 749 types::PropertyMap l_fruCollectionProperty = { 750 {"CollectionStatus", constants::vpdCollectionNotStarted}}; 751 752 vpdSpecificUtility::insertOrMerge(l_interfaces, 753 constants::vpdCollectionInterface, 754 std::move(l_fruCollectionProperty)); 755 756 l_objectInterfaceMap.emplace(std::move(l_fruObjectPath), 757 std::move(l_interfaces)); 758 } 759 760 // Notify PIM 761 if (!dbusUtility::callPIM(move(l_objectInterfaceMap))) 762 { 763 logging::logMessage("Call to PIM failed for VPD file " + i_vpdFilePath); 764 return false; 765 } 766 767 return true; 768 } 769 770 void Worker::processEmbeddedAndSynthesizedFrus(const nlohmann::json& singleFru, 771 types::InterfaceMap& interfaces) 772 { 773 // embedded property(true or false) says whether the subfru is embedded 774 // into the parent fru (or) not. VPD sets Present property only for 775 // embedded frus. If the subfru is not an embedded FRU, the subfru may 776 // or may not be physically present. Those non embedded frus will always 777 // have Present=false irrespective of its physical presence or absence. 778 // Eg: nvme drive in nvme slot is not an embedded FRU. So don't set 779 // Present to true for such sub frus. 780 // Eg: ethernet port is embedded into bmc card. So set Present to true 781 // for such sub frus. Also donot populate present property for embedded 782 // subfru which is synthesized. Currently there is no subfru which are 783 // both embedded and synthesized. But still the case is handled here. 784 785 // Check if its required to handle presence for this FRU. 786 if (singleFru.value("handlePresence", true)) 787 { 788 types::PropertyMap presProp; 789 presProp.emplace("Present", true); 790 vpdSpecificUtility::insertOrMerge( 791 interfaces, "xyz.openbmc_project.Inventory.Item", move(presProp)); 792 } 793 } 794 795 void Worker::processExtraInterfaces(const nlohmann::json& singleFru, 796 types::InterfaceMap& interfaces, 797 const types::VPDMapVariant& parsedVpdMap) 798 { 799 populateInterfaces(singleFru["extraInterfaces"], interfaces, parsedVpdMap); 800 if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 801 { 802 if (singleFru["extraInterfaces"].contains( 803 "xyz.openbmc_project.Inventory.Item.Cpu")) 804 { 805 auto itrToRec = (*ipzVpdMap).find("CP00"); 806 if (itrToRec == (*ipzVpdMap).end()) 807 { 808 return; 809 } 810 811 const std::string pgKeywordValue{ 812 vpdSpecificUtility::getKwVal(itrToRec->second, "PG")}; 813 814 if (!pgKeywordValue.empty()) 815 { 816 if (isCPUIOGoodOnly(pgKeywordValue)) 817 { 818 interfaces["xyz.openbmc_project.Inventory.Item"] 819 ["PrettyName"] = "IO Module"; 820 } 821 } 822 else 823 { 824 throw DataException(std::string(__FUNCTION__) + 825 "Failed to get value for keyword PG"); 826 } 827 } 828 } 829 } 830 831 void Worker::processCopyRecordFlag(const nlohmann::json& singleFru, 832 const types::VPDMapVariant& parsedVpdMap, 833 types::InterfaceMap& interfaces) 834 { 835 if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 836 { 837 for (const auto& record : singleFru["copyRecords"]) 838 { 839 const std::string& recordName = record; 840 if ((*ipzVpdMap).find(recordName) != (*ipzVpdMap).end()) 841 { 842 populateIPZVPDpropertyMap(interfaces, 843 (*ipzVpdMap).at(recordName), 844 constants::ipzVpdInf + recordName); 845 } 846 } 847 } 848 } 849 850 void Worker::processInheritFlag(const types::VPDMapVariant& parsedVpdMap, 851 types::InterfaceMap& interfaces) 852 { 853 if (auto ipzVpdMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 854 { 855 for (const auto& [recordName, kwdValueMap] : *ipzVpdMap) 856 { 857 populateIPZVPDpropertyMap(interfaces, kwdValueMap, 858 constants::ipzVpdInf + recordName); 859 } 860 } 861 else if (auto kwdVpdMap = std::get_if<types::KeywordVpdMap>(&parsedVpdMap)) 862 { 863 populateKwdVPDpropertyMap(*kwdVpdMap, interfaces); 864 } 865 866 if (m_parsedJson.contains("commonInterfaces")) 867 { 868 populateInterfaces(m_parsedJson["commonInterfaces"], interfaces, 869 parsedVpdMap); 870 } 871 } 872 873 bool Worker::processFruWithCCIN(const nlohmann::json& singleFru, 874 const types::VPDMapVariant& parsedVpdMap) 875 { 876 if (auto ipzVPDMap = std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 877 { 878 auto itrToRec = (*ipzVPDMap).find("VINI"); 879 if (itrToRec == (*ipzVPDMap).end()) 880 { 881 return false; 882 } 883 884 std::string ccinFromVpd{ 885 vpdSpecificUtility::getKwVal(itrToRec->second, "CC")}; 886 887 if (ccinFromVpd.empty()) 888 { 889 return false; 890 } 891 892 transform(ccinFromVpd.begin(), ccinFromVpd.end(), ccinFromVpd.begin(), 893 ::toupper); 894 895 std::vector<std::string> ccinList; 896 for (std::string ccin : singleFru["ccin"]) 897 { 898 transform(ccin.begin(), ccin.end(), ccin.begin(), ::toupper); 899 ccinList.push_back(ccin); 900 } 901 902 if (ccinList.empty()) 903 { 904 return false; 905 } 906 907 if (find(ccinList.begin(), ccinList.end(), ccinFromVpd) == 908 ccinList.end()) 909 { 910 return false; 911 } 912 } 913 return true; 914 } 915 916 void Worker::processFunctionalProperty(const std::string& i_inventoryObjPath, 917 types::InterfaceMap& io_interfaces) 918 { 919 if (!dbusUtility::isChassisPowerOn()) 920 { 921 std::array<const char*, 1> l_operationalStatusInf = { 922 constants::operationalStatusInf}; 923 924 auto mapperObjectMap = dbusUtility::getObjectMap( 925 i_inventoryObjPath, l_operationalStatusInf); 926 927 // If the object has been found. Check if it is under PIM. 928 if (mapperObjectMap.size() != 0) 929 { 930 for (const auto& [l_serviceName, l_interfaceLsit] : mapperObjectMap) 931 { 932 if (l_serviceName == constants::pimServiceName) 933 { 934 // The object is already under PIM. No need to process 935 // again. Retain the old value. 936 return; 937 } 938 } 939 } 940 941 // Implies value is not there in D-Bus. Populate it with default 942 // value "true". 943 types::PropertyMap l_functionalProp; 944 l_functionalProp.emplace("Functional", true); 945 vpdSpecificUtility::insertOrMerge(io_interfaces, 946 constants::operationalStatusInf, 947 move(l_functionalProp)); 948 } 949 950 // if chassis is power on. Functional property should be there on D-Bus. 951 // Don't process. 952 return; 953 } 954 955 void Worker::processEnabledProperty(const std::string& i_inventoryObjPath, 956 types::InterfaceMap& io_interfaces) 957 { 958 if (!dbusUtility::isChassisPowerOn()) 959 { 960 std::array<const char*, 1> l_enableInf = {constants::enableInf}; 961 962 auto mapperObjectMap = 963 dbusUtility::getObjectMap(i_inventoryObjPath, l_enableInf); 964 965 // If the object has been found. Check if it is under PIM. 966 if (mapperObjectMap.size() != 0) 967 { 968 for (const auto& [l_serviceName, l_interfaceLsit] : mapperObjectMap) 969 { 970 if (l_serviceName == constants::pimServiceName) 971 { 972 // The object is already under PIM. No need to process 973 // again. Retain the old value. 974 return; 975 } 976 } 977 } 978 979 // Implies value is not there in D-Bus. Populate it with default 980 // value "true". 981 types::PropertyMap l_enabledProp; 982 l_enabledProp.emplace("Enabled", true); 983 vpdSpecificUtility::insertOrMerge(io_interfaces, constants::enableInf, 984 move(l_enabledProp)); 985 } 986 987 // if chassis is power on. Enabled property should be there on D-Bus. 988 // Don't process. 989 return; 990 } 991 992 void Worker::populateDbus(const types::VPDMapVariant& parsedVpdMap, 993 types::ObjectMap& objectInterfaceMap, 994 const std::string& vpdFilePath) 995 { 996 if (vpdFilePath.empty()) 997 { 998 throw std::runtime_error( 999 std::string(__FUNCTION__) + 1000 "Invalid parameter passed to populateDbus API."); 1001 } 1002 1003 // JSON config is mandatory for processing of "if". Add "else" for any 1004 // processing without config JSON. 1005 if (!m_parsedJson.empty()) 1006 { 1007 types::InterfaceMap interfaces; 1008 1009 for (const auto& aFru : m_parsedJson["frus"][vpdFilePath]) 1010 { 1011 const auto& inventoryPath = aFru["inventoryPath"]; 1012 sdbusplus::message::object_path fruObjectPath(inventoryPath); 1013 if (aFru.contains("ccin")) 1014 { 1015 if (!processFruWithCCIN(aFru, parsedVpdMap)) 1016 { 1017 continue; 1018 } 1019 } 1020 1021 if (aFru.value("inherit", true)) 1022 { 1023 processInheritFlag(parsedVpdMap, interfaces); 1024 } 1025 1026 // If specific record needs to be copied. 1027 if (aFru.contains("copyRecords")) 1028 { 1029 processCopyRecordFlag(aFru, parsedVpdMap, interfaces); 1030 } 1031 1032 if (aFru.contains("extraInterfaces")) 1033 { 1034 // Process extra interfaces w.r.t a FRU. 1035 processExtraInterfaces(aFru, interfaces, parsedVpdMap); 1036 } 1037 1038 // Process FRUS which are embedded in the parent FRU and whose VPD 1039 // will be synthesized. 1040 if ((aFru.value("embedded", true)) && 1041 (!aFru.value("synthesized", false))) 1042 { 1043 processEmbeddedAndSynthesizedFrus(aFru, interfaces); 1044 } 1045 1046 processFunctionalProperty(inventoryPath, interfaces); 1047 processEnabledProperty(inventoryPath, interfaces); 1048 1049 // Update collection status as successful 1050 types::PropertyMap l_collectionProperty = { 1051 {"CollectionStatus", constants::vpdCollectionSuccess}}; 1052 1053 vpdSpecificUtility::insertOrMerge(interfaces, 1054 constants::vpdCollectionInterface, 1055 std::move(l_collectionProperty)); 1056 1057 objectInterfaceMap.emplace(std::move(fruObjectPath), 1058 std::move(interfaces)); 1059 } 1060 } 1061 } 1062 1063 std::string Worker::createAssetTagString( 1064 const types::VPDMapVariant& i_parsedVpdMap) 1065 { 1066 std::string l_assetTag; 1067 1068 // system VPD will be in IPZ format. 1069 if (auto l_parsedVpdMap = std::get_if<types::IPZVpdMap>(&i_parsedVpdMap)) 1070 { 1071 auto l_itrToVsys = (*l_parsedVpdMap).find(constants::recVSYS); 1072 if (l_itrToVsys != (*l_parsedVpdMap).end()) 1073 { 1074 const std::string l_tmKwdValue{vpdSpecificUtility::getKwVal( 1075 l_itrToVsys->second, constants::kwdTM)}; 1076 1077 if (l_tmKwdValue.empty()) 1078 { 1079 throw std::runtime_error( 1080 std::string("Failed to get value for keyword [") + 1081 constants::kwdTM + 1082 std::string("] while creating Asset tag.")); 1083 } 1084 1085 const std::string l_seKwdValue{vpdSpecificUtility::getKwVal( 1086 l_itrToVsys->second, constants::kwdSE)}; 1087 1088 if (l_seKwdValue.empty()) 1089 { 1090 throw std::runtime_error( 1091 std::string("Failed to get value for keyword [") + 1092 constants::kwdSE + 1093 std::string("] while creating Asset tag.")); 1094 } 1095 1096 l_assetTag = std::string{"Server-"} + l_tmKwdValue + 1097 std::string{"-"} + l_seKwdValue; 1098 } 1099 else 1100 { 1101 throw std::runtime_error( 1102 "VSYS record not found in parsed VPD map to create Asset tag."); 1103 } 1104 } 1105 else 1106 { 1107 throw std::runtime_error( 1108 "Invalid VPD type recieved to create Asset tag."); 1109 } 1110 1111 return l_assetTag; 1112 } 1113 1114 void Worker::publishSystemVPD(const types::VPDMapVariant& parsedVpdMap) 1115 { 1116 types::ObjectMap objectInterfaceMap; 1117 1118 if (std::get_if<types::IPZVpdMap>(&parsedVpdMap)) 1119 { 1120 populateDbus(parsedVpdMap, objectInterfaceMap, SYSTEM_VPD_FILE_PATH); 1121 1122 try 1123 { 1124 if (m_isFactoryResetDone) 1125 { 1126 const auto& l_assetTag = createAssetTagString(parsedVpdMap); 1127 1128 auto l_itrToSystemPath = objectInterfaceMap.find( 1129 sdbusplus::message::object_path(constants::systemInvPath)); 1130 if (l_itrToSystemPath == objectInterfaceMap.end()) 1131 { 1132 throw std::runtime_error( 1133 "Asset tag update failed. System Path not found in object map."); 1134 } 1135 1136 types::PropertyMap l_assetTagProperty; 1137 l_assetTagProperty.emplace("AssetTag", l_assetTag); 1138 1139 (l_itrToSystemPath->second) 1140 .emplace(constants::assetTagInf, 1141 std::move(l_assetTagProperty)); 1142 } 1143 } 1144 catch (const std::exception& l_ex) 1145 { 1146 EventLogger::createSyncPel( 1147 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 1148 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), 1149 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1150 } 1151 1152 // Notify PIM 1153 if (!dbusUtility::callPIM(move(objectInterfaceMap))) 1154 { 1155 throw std::runtime_error("Call to PIM failed for system VPD"); 1156 } 1157 } 1158 else 1159 { 1160 throw DataException("Invalid format of parsed VPD map."); 1161 } 1162 } 1163 1164 bool Worker::processPreAction(const std::string& i_vpdFilePath, 1165 const std::string& i_flagToProcess) 1166 { 1167 if (i_vpdFilePath.empty() || i_flagToProcess.empty()) 1168 { 1169 logging::logMessage( 1170 "Invalid input parameter. Abort processing pre action"); 1171 return false; 1172 } 1173 1174 if ((!jsonUtility::executeBaseAction(m_parsedJson, "preAction", 1175 i_vpdFilePath, i_flagToProcess)) && 1176 (i_flagToProcess.compare("collection") == constants::STR_CMP_SUCCESS)) 1177 { 1178 // TODO: Need a way to delete inventory object from Dbus and persisted 1179 // data section in case any FRU is not present or there is any 1180 // problem in collecting it. Once it has been deleted, it can be 1181 // re-created in the flow of priming the inventory. This needs to be 1182 // done either here or in the exception section of "parseAndPublishVPD" 1183 // API. Any failure in the process of collecting FRU will land up in the 1184 // excpetion of "parseAndPublishVPD". 1185 1186 // If the FRU is not there, clear the VINI/CCIN data. 1187 // Enity manager probes for this keyword to look for this 1188 // FRU, now if the data is persistent on BMC and FRU is 1189 // removed this can lead to ambiguity. Hence clearing this 1190 // Keyword if FRU is absent. 1191 const auto& inventoryPath = 1192 m_parsedJson["frus"][i_vpdFilePath].at(0).value("inventoryPath", 1193 ""); 1194 1195 if (!inventoryPath.empty()) 1196 { 1197 types::ObjectMap l_pimObjMap{ 1198 {inventoryPath, 1199 {{constants::kwdVpdInf, 1200 {{constants::kwdCCIN, types::BinaryVector{}}}}}}}; 1201 1202 if (!dbusUtility::callPIM(std::move(l_pimObjMap))) 1203 { 1204 logging::logMessage( 1205 "Call to PIM failed for file " + i_vpdFilePath); 1206 } 1207 } 1208 else 1209 { 1210 logging::logMessage( 1211 "Inventory path is empty in Json for file " + i_vpdFilePath); 1212 } 1213 1214 return false; 1215 } 1216 return true; 1217 } 1218 1219 bool Worker::processPostAction( 1220 const std::string& i_vpdFruPath, const std::string& i_flagToProcess, 1221 const std::optional<types::VPDMapVariant> i_parsedVpd) 1222 { 1223 if (i_vpdFruPath.empty() || i_flagToProcess.empty()) 1224 { 1225 logging::logMessage( 1226 "Invalid input parameter. Abort processing post action"); 1227 return false; 1228 } 1229 1230 // Check if post action tag is to be triggered in the flow of collection 1231 // based on some CCIN value? 1232 if (m_parsedJson["frus"][i_vpdFruPath] 1233 .at(0)["postAction"][i_flagToProcess] 1234 .contains("ccin")) 1235 { 1236 if (!i_parsedVpd.has_value()) 1237 { 1238 logging::logMessage("Empty VPD Map"); 1239 return false; 1240 } 1241 1242 // CCIN match is required to process post action for this FRU as it 1243 // contains the flag. 1244 if (!vpdSpecificUtility::findCcinInVpd( 1245 m_parsedJson["frus"][i_vpdFruPath].at( 1246 0)["postAction"]["collection"], 1247 i_parsedVpd.value())) 1248 { 1249 // If CCIN is not found, implies post action processing is not 1250 // required for this FRU. Let the flow continue. 1251 return true; 1252 } 1253 } 1254 1255 if (!jsonUtility::executeBaseAction(m_parsedJson, "postAction", 1256 i_vpdFruPath, i_flagToProcess)) 1257 { 1258 logging::logMessage( 1259 "Execution of post action failed for path: " + i_vpdFruPath); 1260 1261 // If post action was required and failed only in that case return 1262 // false. In all other case post action is considered passed. 1263 return false; 1264 } 1265 1266 return true; 1267 } 1268 1269 types::VPDMapVariant Worker::parseVpdFile(const std::string& i_vpdFilePath) 1270 { 1271 try 1272 { 1273 if (i_vpdFilePath.empty()) 1274 { 1275 throw std::runtime_error( 1276 std::string(__FUNCTION__) + 1277 " Empty VPD file path passed. Abort processing"); 1278 } 1279 1280 bool isPreActionRequired = false; 1281 if (jsonUtility::isActionRequired(m_parsedJson, i_vpdFilePath, 1282 "preAction", "collection")) 1283 { 1284 isPreActionRequired = true; 1285 if (!processPreAction(i_vpdFilePath, "collection")) 1286 { 1287 throw std::runtime_error( 1288 std::string(__FUNCTION__) + " Pre-Action failed"); 1289 } 1290 } 1291 1292 if (!std::filesystem::exists(i_vpdFilePath)) 1293 { 1294 if (isPreActionRequired) 1295 { 1296 throw std::runtime_error( 1297 std::string(__FUNCTION__) + " Could not find file path " + 1298 i_vpdFilePath + "Skipping parser trigger for the EEPROM"); 1299 } 1300 return types::VPDMapVariant{}; 1301 } 1302 1303 std::shared_ptr<Parser> vpdParser = 1304 std::make_shared<Parser>(i_vpdFilePath, m_parsedJson); 1305 1306 types::VPDMapVariant l_parsedVpd = vpdParser->parse(); 1307 1308 // Before returning, as collection is over, check if FRU qualifies for 1309 // any post action in the flow of collection. 1310 // Note: Don't change the order, post action needs to be processed only 1311 // after collection for FRU is successfully done. 1312 if (jsonUtility::isActionRequired(m_parsedJson, i_vpdFilePath, 1313 "postAction", "collection")) 1314 { 1315 if (!processPostAction(i_vpdFilePath, "collection", l_parsedVpd)) 1316 { 1317 // Post action was required but failed while executing. 1318 // Behaviour can be undefined. 1319 EventLogger::createSyncPel( 1320 types::ErrorType::InternalFailure, 1321 types::SeverityType::Warning, __FILE__, __FUNCTION__, 0, 1322 std::string("Required post action failed for path [" + 1323 i_vpdFilePath + "]"), 1324 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1325 } 1326 } 1327 1328 return l_parsedVpd; 1329 } 1330 catch (std::exception& l_ex) 1331 { 1332 // If post fail action is required, execute it. 1333 if (jsonUtility::isActionRequired(m_parsedJson, i_vpdFilePath, 1334 "postFailAction", "collection")) 1335 { 1336 if (!jsonUtility::executePostFailAction(m_parsedJson, i_vpdFilePath, 1337 "collection")) 1338 { 1339 throw std::runtime_error( 1340 std::string(__FUNCTION__) + "VPD parsing failed for " + 1341 i_vpdFilePath + " due to error: " + l_ex.what() + 1342 ". Post Fail Action also failed, aborting collection for this FRU"); 1343 } 1344 } 1345 1346 throw std::runtime_error( 1347 std::string(__FUNCTION__) + "VPD parsing failed for " + 1348 i_vpdFilePath + " due to error: " + l_ex.what()); 1349 } 1350 } 1351 1352 std::tuple<bool, std::string> Worker::parseAndPublishVPD( 1353 const std::string& i_vpdFilePath) 1354 { 1355 std::string l_inventoryPath{}; 1356 1357 try 1358 { 1359 m_semaphore.acquire(); 1360 1361 // Thread launched. 1362 m_mutex.lock(); 1363 m_activeCollectionThreadCount++; 1364 m_mutex.unlock(); 1365 1366 // Set CollectionStatus as InProgress. Since it's an intermediate state 1367 // D-bus set-property call is good enough to update the status. 1368 l_inventoryPath = jsonUtility::getInventoryObjPathFromJson( 1369 m_parsedJson, i_vpdFilePath); 1370 1371 if (!l_inventoryPath.empty()) 1372 { 1373 if (!dbusUtility::writeDbusProperty( 1374 jsonUtility::getServiceName(m_parsedJson, l_inventoryPath), 1375 l_inventoryPath, constants::vpdCollectionInterface, 1376 "CollectionStatus", 1377 types::DbusVariantType{constants::vpdCollectionInProgress})) 1378 { 1379 logging::logMessage( 1380 "Unable to set CollectionStatus as InProgress for " + 1381 i_vpdFilePath + ". Error : " + "DBus write failed"); 1382 } 1383 } 1384 1385 const types::VPDMapVariant& parsedVpdMap = parseVpdFile(i_vpdFilePath); 1386 if (!std::holds_alternative<std::monostate>(parsedVpdMap)) 1387 { 1388 types::ObjectMap objectInterfaceMap; 1389 populateDbus(parsedVpdMap, objectInterfaceMap, i_vpdFilePath); 1390 1391 // Notify PIM 1392 if (!dbusUtility::callPIM(move(objectInterfaceMap))) 1393 { 1394 throw std::runtime_error( 1395 std::string(__FUNCTION__) + 1396 "Call to PIM failed while publishing VPD."); 1397 } 1398 } 1399 else 1400 { 1401 logging::logMessage("Empty parsedVpdMap recieved for path [" + 1402 i_vpdFilePath + "]. Check PEL for reason."); 1403 } 1404 } 1405 catch (const std::exception& ex) 1406 { 1407 // Notify FRU's VPD CollectionStatus as Failure 1408 if (!dbusUtility::notifyFRUCollectionStatus( 1409 l_inventoryPath, constants::vpdCollectionFailure)) 1410 { 1411 logging::logMessage( 1412 "Call to PIM Notify method failed to update Collection status as Failure for " + 1413 i_vpdFilePath); 1414 } 1415 1416 // handle all the exceptions internally. Return only true/false 1417 // based on status of execution. 1418 if (typeid(ex) == std::type_index(typeid(DataException))) 1419 { 1420 // In case of pass1 planar, VPD can be corrupted on PCIe cards. Skip 1421 // logging error for these cases. 1422 if (vpdSpecificUtility::isPass1Planar()) 1423 { 1424 const std::string& l_invPathLeafValue = 1425 sdbusplus::message::object_path( 1426 jsonUtility::getInventoryObjPathFromJson(m_parsedJson, 1427 i_vpdFilePath)) 1428 .filename(); 1429 1430 if ((l_invPathLeafValue.find("pcie_card", 0) != 1431 std::string::npos)) 1432 { 1433 // skip logging any PEL for PCIe cards on pass 1 planar. 1434 return std::make_tuple(false, i_vpdFilePath); 1435 } 1436 } 1437 } 1438 1439 EventLogger::createSyncPel( 1440 EventLogger::getErrorType(ex), types::SeverityType::Informational, 1441 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(ex), 1442 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1443 1444 // TODO: Figure out a way to clear data in case of any failure at 1445 // runtime. 1446 1447 // set present property to false for any error case. In future this will 1448 // be replaced by presence logic. 1449 // Update Present property for this FRU only if we handle Present 1450 // property for the FRU. 1451 if (isPresentPropertyHandlingRequired( 1452 m_parsedJson["frus"][i_vpdFilePath].at(0))) 1453 { 1454 setPresentProperty(i_vpdFilePath, false); 1455 } 1456 1457 m_semaphore.release(); 1458 return std::make_tuple(false, i_vpdFilePath); 1459 } 1460 m_semaphore.release(); 1461 return std::make_tuple(true, i_vpdFilePath); 1462 } 1463 1464 bool Worker::skipPathForCollection(const std::string& i_vpdFilePath) 1465 { 1466 if (i_vpdFilePath.empty()) 1467 { 1468 return true; 1469 } 1470 1471 // skip processing of system VPD again as it has been already collected. 1472 if (i_vpdFilePath == SYSTEM_VPD_FILE_PATH) 1473 { 1474 return true; 1475 } 1476 1477 if (dbusUtility::isChassisPowerOn()) 1478 { 1479 // If chassis is powered on, skip collecting FRUs which are 1480 // powerOffOnly. 1481 if (jsonUtility::isFruPowerOffOnly(m_parsedJson, i_vpdFilePath)) 1482 { 1483 return true; 1484 } 1485 1486 const std::string& l_invPathLeafValue = 1487 sdbusplus::message::object_path( 1488 jsonUtility::getInventoryObjPathFromJson(m_parsedJson, 1489 i_vpdFilePath)) 1490 .filename(); 1491 1492 if ((l_invPathLeafValue.find("pcie_card", 0) != std::string::npos)) 1493 { 1494 return true; 1495 } 1496 } 1497 1498 return false; 1499 } 1500 1501 void Worker::collectFrusFromJson() 1502 { 1503 // A parsed JSON file should be present to pick FRUs EEPROM paths 1504 if (m_parsedJson.empty()) 1505 { 1506 throw JsonException( 1507 std::string(__FUNCTION__) + 1508 ": Config JSON is mandatory for processing of FRUs through this API.", 1509 m_configJsonPath); 1510 } 1511 1512 const nlohmann::json& listOfFrus = 1513 m_parsedJson["frus"].get_ref<const nlohmann::json::object_t&>(); 1514 1515 for (const auto& itemFRUS : listOfFrus.items()) 1516 { 1517 const std::string& vpdFilePath = itemFRUS.key(); 1518 1519 if (skipPathForCollection(vpdFilePath)) 1520 { 1521 continue; 1522 } 1523 1524 try 1525 { 1526 std::thread{[vpdFilePath, this]() { 1527 const auto& l_parseResult = parseAndPublishVPD(vpdFilePath); 1528 1529 m_mutex.lock(); 1530 m_activeCollectionThreadCount--; 1531 m_mutex.unlock(); 1532 1533 if (!m_activeCollectionThreadCount) 1534 { 1535 m_isAllFruCollected = true; 1536 } 1537 }}.detach(); 1538 } 1539 catch (const std::exception& l_ex) 1540 { 1541 // add vpdFilePath(EEPROM path) to failed list 1542 m_failedEepromPaths.push_front(vpdFilePath); 1543 } 1544 } 1545 } 1546 1547 // ToDo: Move the API under IBM_SYSTEM 1548 void Worker::performBackupAndRestore(types::VPDMapVariant& io_srcVpdMap) 1549 { 1550 try 1551 { 1552 std::string l_backupAndRestoreCfgFilePath = 1553 m_parsedJson.value("backupRestoreConfigPath", ""); 1554 1555 nlohmann::json l_backupAndRestoreCfgJsonObj = 1556 jsonUtility::getParsedJson(l_backupAndRestoreCfgFilePath); 1557 1558 if (l_backupAndRestoreCfgJsonObj.empty()) 1559 { 1560 throw JsonException("JSON parsing failed", 1561 l_backupAndRestoreCfgFilePath); 1562 } 1563 1564 // check if either of "source" or "destination" has inventory path. 1565 // this indicates that this sytem has System VPD on hardware 1566 // and other copy on D-Bus (BMC cache). 1567 if (!l_backupAndRestoreCfgJsonObj.empty() && 1568 ((l_backupAndRestoreCfgJsonObj.contains("source") && 1569 l_backupAndRestoreCfgJsonObj["source"].contains( 1570 "inventoryPath")) || 1571 (l_backupAndRestoreCfgJsonObj.contains("destination") && 1572 l_backupAndRestoreCfgJsonObj["destination"].contains( 1573 "inventoryPath")))) 1574 { 1575 BackupAndRestore l_backupAndRestoreObj(m_parsedJson); 1576 auto [l_srcVpdVariant, 1577 l_dstVpdVariant] = l_backupAndRestoreObj.backupAndRestore(); 1578 1579 // ToDo: Revisit is this check is required or not. 1580 if (auto l_srcVpdMap = 1581 std::get_if<types::IPZVpdMap>(&l_srcVpdVariant); 1582 l_srcVpdMap && !(*l_srcVpdMap).empty()) 1583 { 1584 io_srcVpdMap = std::move(l_srcVpdVariant); 1585 } 1586 } 1587 } 1588 catch (const std::exception& l_ex) 1589 { 1590 EventLogger::createSyncPel( 1591 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 1592 __FILE__, __FUNCTION__, 0, 1593 std::string( 1594 "Exception caught while backup and restore VPD keyword's.") + 1595 EventLogger::getErrorMsg(l_ex), 1596 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1597 } 1598 } 1599 1600 void Worker::deleteFruVpd(const std::string& i_dbusObjPath) 1601 { 1602 if (i_dbusObjPath.empty()) 1603 { 1604 throw std::runtime_error("Given DBus object path is empty."); 1605 } 1606 1607 const std::string& l_fruPath = 1608 jsonUtility::getFruPathFromJson(m_parsedJson, i_dbusObjPath); 1609 1610 try 1611 { 1612 auto l_presentPropValue = dbusUtility::readDbusProperty( 1613 constants::pimServiceName, i_dbusObjPath, 1614 constants::inventoryItemInf, "Present"); 1615 1616 if (auto l_value = std::get_if<bool>(&l_presentPropValue)) 1617 { 1618 if (!(*l_value)) 1619 { 1620 throw std::runtime_error("Given FRU is not present"); 1621 } 1622 else 1623 { 1624 if (jsonUtility::isActionRequired(m_parsedJson, l_fruPath, 1625 "preAction", "deletion")) 1626 { 1627 if (!processPreAction(l_fruPath, "deletion")) 1628 { 1629 throw std::runtime_error("Pre action failed"); 1630 } 1631 } 1632 1633 std::vector<std::string> l_interfaceList{ 1634 constants::operationalStatusInf}; 1635 1636 types::MapperGetSubTree l_subTreeMap = 1637 dbusUtility::getObjectSubTree(i_dbusObjPath, 0, 1638 l_interfaceList); 1639 1640 types::ObjectMap l_objectMap; 1641 1642 // Updates VPD specific interfaces property value under PIM for 1643 // sub FRUs. 1644 for (const auto& [l_objectPath, l_serviceInterfaceMap] : 1645 l_subTreeMap) 1646 { 1647 types::InterfaceMap l_interfaceMap; 1648 vpdSpecificUtility::resetDataUnderPIM(l_objectPath, 1649 l_interfaceMap); 1650 l_objectMap.emplace(l_objectPath, 1651 std::move(l_interfaceMap)); 1652 } 1653 1654 types::InterfaceMap l_interfaceMap; 1655 vpdSpecificUtility::resetDataUnderPIM(i_dbusObjPath, 1656 l_interfaceMap); 1657 1658 l_objectMap.emplace(i_dbusObjPath, std::move(l_interfaceMap)); 1659 1660 if (!dbusUtility::callPIM(std::move(l_objectMap))) 1661 { 1662 throw std::runtime_error("Call to PIM failed."); 1663 } 1664 1665 if (jsonUtility::isActionRequired(m_parsedJson, l_fruPath, 1666 "postAction", "deletion")) 1667 { 1668 if (!processPostAction(l_fruPath, "deletion")) 1669 { 1670 throw std::runtime_error("Post action failed"); 1671 } 1672 } 1673 } 1674 } 1675 else 1676 { 1677 logging::logMessage( 1678 "Can't process delete VPD for FRU [" + i_dbusObjPath + 1679 "] as unable to read present property"); 1680 return; 1681 } 1682 1683 logging::logMessage( 1684 "Successfully completed deletion of FRU VPD for " + i_dbusObjPath); 1685 } 1686 catch (const std::exception& l_ex) 1687 { 1688 if (jsonUtility::isActionRequired(m_parsedJson, l_fruPath, 1689 "postFailAction", "deletion")) 1690 { 1691 if (!jsonUtility::executePostFailAction(m_parsedJson, l_fruPath, 1692 "deletion")) 1693 { 1694 logging::logMessage( 1695 "Post fail action failed for: " + i_dbusObjPath); 1696 } 1697 } 1698 1699 logging::logMessage("Failed to delete VPD for FRU : " + i_dbusObjPath + 1700 " error: " + std::string(l_ex.what())); 1701 } 1702 } 1703 1704 void Worker::setPresentProperty(const std::string& i_vpdPath, 1705 const bool& i_value) 1706 { 1707 try 1708 { 1709 if (i_vpdPath.empty()) 1710 { 1711 throw std::runtime_error( 1712 "Path is empty. Can't set present property"); 1713 } 1714 1715 types::ObjectMap l_objectInterfaceMap; 1716 1717 // If the given path is EEPROM path. 1718 if (m_parsedJson["frus"].contains(i_vpdPath)) 1719 { 1720 for (const auto& l_Fru : m_parsedJson["frus"][i_vpdPath]) 1721 { 1722 sdbusplus::message::object_path l_fruObjectPath( 1723 l_Fru["inventoryPath"]); 1724 1725 types::PropertyMap l_propertyValueMap; 1726 l_propertyValueMap.emplace("Present", i_value); 1727 1728 types::InterfaceMap l_interfaces; 1729 vpdSpecificUtility::insertOrMerge(l_interfaces, 1730 constants::inventoryItemInf, 1731 move(l_propertyValueMap)); 1732 1733 l_objectInterfaceMap.emplace(std::move(l_fruObjectPath), 1734 std::move(l_interfaces)); 1735 } 1736 } 1737 else 1738 { 1739 // consider it as an inventory path. 1740 if (i_vpdPath.find(constants::pimPath) != constants::VALUE_0) 1741 { 1742 throw std::runtime_error( 1743 "Invalid inventory path: " + i_vpdPath); 1744 } 1745 1746 types::PropertyMap l_propertyValueMap; 1747 l_propertyValueMap.emplace("Present", i_value); 1748 1749 types::InterfaceMap l_interfaces; 1750 vpdSpecificUtility::insertOrMerge(l_interfaces, 1751 constants::inventoryItemInf, 1752 move(l_propertyValueMap)); 1753 1754 l_objectInterfaceMap.emplace(i_vpdPath, std::move(l_interfaces)); 1755 } 1756 1757 // Notify PIM 1758 if (!dbusUtility::callPIM(move(l_objectInterfaceMap))) 1759 { 1760 throw DbusException( 1761 std::string(__FUNCTION__) + 1762 "Call to PIM failed while setting present property for path " + 1763 i_vpdPath); 1764 } 1765 } 1766 catch (const std::exception& l_ex) 1767 { 1768 EventLogger::createSyncPel( 1769 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 1770 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), 1771 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1772 } 1773 } 1774 1775 void Worker::performVpdRecollection() 1776 { 1777 try 1778 { 1779 // Check if system config JSON is present 1780 if (m_parsedJson.empty()) 1781 { 1782 throw std::runtime_error( 1783 "System config json object is empty, can't process recollection."); 1784 } 1785 1786 const auto& l_frusReplaceableAtStandby = 1787 jsonUtility::getListOfFrusReplaceableAtStandby(m_parsedJson); 1788 1789 for (const auto& l_fruInventoryPath : l_frusReplaceableAtStandby) 1790 { 1791 // ToDo: Add some logic/trace to know the flow to 1792 // collectSingleFruVpd has been directed via 1793 // performVpdRecollection. 1794 collectSingleFruVpd(l_fruInventoryPath); 1795 } 1796 return; 1797 } 1798 1799 catch (const std::exception& l_ex) 1800 { 1801 // TODO Log PEL 1802 logging::logMessage( 1803 "VPD recollection failed with error: " + std::string(l_ex.what())); 1804 } 1805 } 1806 1807 void Worker::collectSingleFruVpd( 1808 const sdbusplus::message::object_path& i_dbusObjPath) 1809 { 1810 try 1811 { 1812 // Check if system config JSON is present 1813 if (m_parsedJson.empty()) 1814 { 1815 logging::logMessage( 1816 "System config JSON object not present. Single FRU VPD collection is not performed for " + 1817 std::string(i_dbusObjPath)); 1818 return; 1819 } 1820 1821 // Get FRU path for the given D-bus object path from JSON 1822 const std::string& l_fruPath = 1823 jsonUtility::getFruPathFromJson(m_parsedJson, i_dbusObjPath); 1824 1825 if (l_fruPath.empty()) 1826 { 1827 logging::logMessage( 1828 "D-bus object path not present in JSON. Single FRU VPD collection is not performed for " + 1829 std::string(i_dbusObjPath)); 1830 return; 1831 } 1832 1833 // Check if host is up and running 1834 if (dbusUtility::isHostRunning()) 1835 { 1836 if (!jsonUtility::isFruReplaceableAtRuntime(m_parsedJson, 1837 l_fruPath)) 1838 { 1839 logging::logMessage( 1840 "Given FRU is not replaceable at host runtime. Single FRU VPD collection is not performed for " + 1841 std::string(i_dbusObjPath)); 1842 return; 1843 } 1844 } 1845 else if (dbusUtility::isBMCReady()) 1846 { 1847 if (!jsonUtility::isFruReplaceableAtStandby(m_parsedJson, 1848 l_fruPath) && 1849 (!jsonUtility::isFruReplaceableAtRuntime(m_parsedJson, 1850 l_fruPath))) 1851 { 1852 logging::logMessage( 1853 "Given FRU is neither replaceable at standby nor replaceable at runtime. Single FRU VPD collection is not performed for " + 1854 std::string(i_dbusObjPath)); 1855 return; 1856 } 1857 } 1858 1859 // Set CollectionStatus as InProgress. Since it's an intermediate state 1860 // D-bus set-property call is good enough to update the status. 1861 const std::string& l_collStatusProp = "CollectionStatus"; 1862 1863 if (!dbusUtility::writeDbusProperty( 1864 jsonUtility::getServiceName(m_parsedJson, 1865 std::string(i_dbusObjPath)), 1866 std::string(i_dbusObjPath), constants::vpdCollectionInterface, 1867 l_collStatusProp, 1868 types::DbusVariantType{constants::vpdCollectionInProgress})) 1869 { 1870 logging::logMessage( 1871 "Unable to set CollectionStatus as InProgress for " + 1872 std::string(i_dbusObjPath) + 1873 ". Continue single FRU VPD collection."); 1874 } 1875 1876 // Parse VPD 1877 types::VPDMapVariant l_parsedVpd = parseVpdFile(l_fruPath); 1878 1879 // If l_parsedVpd is pointing to std::monostate 1880 if (l_parsedVpd.index() == 0) 1881 { 1882 throw std::runtime_error( 1883 "VPD parsing failed for " + std::string(i_dbusObjPath)); 1884 } 1885 1886 // Get D-bus object map from worker class 1887 types::ObjectMap l_dbusObjectMap; 1888 populateDbus(l_parsedVpd, l_dbusObjectMap, l_fruPath); 1889 1890 if (l_dbusObjectMap.empty()) 1891 { 1892 throw std::runtime_error( 1893 "Failed to create D-bus object map. Single FRU VPD collection failed for " + 1894 std::string(i_dbusObjPath)); 1895 } 1896 1897 // Call PIM's Notify method 1898 if (!dbusUtility::callPIM(move(l_dbusObjectMap))) 1899 { 1900 throw std::runtime_error( 1901 "Notify PIM failed. Single FRU VPD collection failed for " + 1902 std::string(i_dbusObjPath)); 1903 } 1904 } 1905 catch (const std::exception& l_error) 1906 { 1907 // Notify FRU's VPD CollectionStatus as Failure 1908 if (!dbusUtility::notifyFRUCollectionStatus( 1909 std::string(i_dbusObjPath), constants::vpdCollectionFailure)) 1910 { 1911 logging::logMessage( 1912 "Call to PIM Notify method failed to update Collection status as Failure for " + 1913 std::string(i_dbusObjPath)); 1914 } 1915 1916 // TODO: Log PEL 1917 logging::logMessage(std::string(l_error.what())); 1918 } 1919 } 1920 } // namespace vpd 1921