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 setCollectionStatusProperty(i_vpdFilePath, 1408 constants::vpdCollectionFailure); 1409 1410 // handle all the exceptions internally. Return only true/false 1411 // based on status of execution. 1412 if (typeid(ex) == std::type_index(typeid(DataException))) 1413 { 1414 // In case of pass1 planar, VPD can be corrupted on PCIe cards. Skip 1415 // logging error for these cases. 1416 if (vpdSpecificUtility::isPass1Planar()) 1417 { 1418 const std::string& l_invPathLeafValue = 1419 sdbusplus::message::object_path( 1420 jsonUtility::getInventoryObjPathFromJson(m_parsedJson, 1421 i_vpdFilePath)) 1422 .filename(); 1423 1424 if ((l_invPathLeafValue.find("pcie_card", 0) != 1425 std::string::npos)) 1426 { 1427 // skip logging any PEL for PCIe cards on pass 1 planar. 1428 return std::make_tuple(false, i_vpdFilePath); 1429 } 1430 } 1431 } 1432 1433 EventLogger::createSyncPel( 1434 EventLogger::getErrorType(ex), types::SeverityType::Informational, 1435 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(ex), 1436 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1437 1438 // TODO: Figure out a way to clear data in case of any failure at 1439 // runtime. 1440 1441 // set present property to false for any error case. In future this will 1442 // be replaced by presence logic. 1443 // Update Present property for this FRU only if we handle Present 1444 // property for the FRU. 1445 if (isPresentPropertyHandlingRequired( 1446 m_parsedJson["frus"][i_vpdFilePath].at(0))) 1447 { 1448 setPresentProperty(i_vpdFilePath, false); 1449 } 1450 1451 m_semaphore.release(); 1452 return std::make_tuple(false, i_vpdFilePath); 1453 } 1454 m_semaphore.release(); 1455 return std::make_tuple(true, i_vpdFilePath); 1456 } 1457 1458 bool Worker::skipPathForCollection(const std::string& i_vpdFilePath) 1459 { 1460 if (i_vpdFilePath.empty()) 1461 { 1462 return true; 1463 } 1464 1465 // skip processing of system VPD again as it has been already collected. 1466 if (i_vpdFilePath == SYSTEM_VPD_FILE_PATH) 1467 { 1468 return true; 1469 } 1470 1471 if (dbusUtility::isChassisPowerOn()) 1472 { 1473 // If chassis is powered on, skip collecting FRUs which are 1474 // powerOffOnly. 1475 if (jsonUtility::isFruPowerOffOnly(m_parsedJson, i_vpdFilePath)) 1476 { 1477 return true; 1478 } 1479 1480 const std::string& l_invPathLeafValue = 1481 sdbusplus::message::object_path( 1482 jsonUtility::getInventoryObjPathFromJson(m_parsedJson, 1483 i_vpdFilePath)) 1484 .filename(); 1485 1486 if ((l_invPathLeafValue.find("pcie_card", 0) != std::string::npos)) 1487 { 1488 return true; 1489 } 1490 } 1491 1492 return false; 1493 } 1494 1495 void Worker::collectFrusFromJson() 1496 { 1497 // A parsed JSON file should be present to pick FRUs EEPROM paths 1498 if (m_parsedJson.empty()) 1499 { 1500 throw JsonException( 1501 std::string(__FUNCTION__) + 1502 ": Config JSON is mandatory for processing of FRUs through this API.", 1503 m_configJsonPath); 1504 } 1505 1506 const nlohmann::json& listOfFrus = 1507 m_parsedJson["frus"].get_ref<const nlohmann::json::object_t&>(); 1508 1509 for (const auto& itemFRUS : listOfFrus.items()) 1510 { 1511 const std::string& vpdFilePath = itemFRUS.key(); 1512 1513 if (skipPathForCollection(vpdFilePath)) 1514 { 1515 continue; 1516 } 1517 1518 try 1519 { 1520 std::thread{[vpdFilePath, this]() { 1521 const auto& l_parseResult = parseAndPublishVPD(vpdFilePath); 1522 1523 m_mutex.lock(); 1524 m_activeCollectionThreadCount--; 1525 m_mutex.unlock(); 1526 1527 if (!m_activeCollectionThreadCount) 1528 { 1529 m_isAllFruCollected = true; 1530 } 1531 }}.detach(); 1532 } 1533 catch (const std::exception& l_ex) 1534 { 1535 // add vpdFilePath(EEPROM path) to failed list 1536 m_failedEepromPaths.push_front(vpdFilePath); 1537 } 1538 } 1539 } 1540 1541 // ToDo: Move the API under IBM_SYSTEM 1542 void Worker::performBackupAndRestore(types::VPDMapVariant& io_srcVpdMap) 1543 { 1544 try 1545 { 1546 std::string l_backupAndRestoreCfgFilePath = 1547 m_parsedJson.value("backupRestoreConfigPath", ""); 1548 1549 nlohmann::json l_backupAndRestoreCfgJsonObj = 1550 jsonUtility::getParsedJson(l_backupAndRestoreCfgFilePath); 1551 1552 if (l_backupAndRestoreCfgJsonObj.empty()) 1553 { 1554 throw JsonException("JSON parsing failed", 1555 l_backupAndRestoreCfgFilePath); 1556 } 1557 1558 // check if either of "source" or "destination" has inventory path. 1559 // this indicates that this sytem has System VPD on hardware 1560 // and other copy on D-Bus (BMC cache). 1561 if (!l_backupAndRestoreCfgJsonObj.empty() && 1562 ((l_backupAndRestoreCfgJsonObj.contains("source") && 1563 l_backupAndRestoreCfgJsonObj["source"].contains( 1564 "inventoryPath")) || 1565 (l_backupAndRestoreCfgJsonObj.contains("destination") && 1566 l_backupAndRestoreCfgJsonObj["destination"].contains( 1567 "inventoryPath")))) 1568 { 1569 BackupAndRestore l_backupAndRestoreObj(m_parsedJson); 1570 auto [l_srcVpdVariant, 1571 l_dstVpdVariant] = l_backupAndRestoreObj.backupAndRestore(); 1572 1573 // ToDo: Revisit is this check is required or not. 1574 if (auto l_srcVpdMap = 1575 std::get_if<types::IPZVpdMap>(&l_srcVpdVariant); 1576 l_srcVpdMap && !(*l_srcVpdMap).empty()) 1577 { 1578 io_srcVpdMap = std::move(l_srcVpdVariant); 1579 } 1580 } 1581 } 1582 catch (const std::exception& l_ex) 1583 { 1584 EventLogger::createSyncPel( 1585 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 1586 __FILE__, __FUNCTION__, 0, 1587 std::string( 1588 "Exception caught while backup and restore VPD keyword's.") + 1589 EventLogger::getErrorMsg(l_ex), 1590 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1591 } 1592 } 1593 1594 void Worker::deleteFruVpd(const std::string& i_dbusObjPath) 1595 { 1596 if (i_dbusObjPath.empty()) 1597 { 1598 throw std::runtime_error("Given DBus object path is empty."); 1599 } 1600 1601 const std::string& l_fruPath = 1602 jsonUtility::getFruPathFromJson(m_parsedJson, i_dbusObjPath); 1603 1604 try 1605 { 1606 auto l_presentPropValue = dbusUtility::readDbusProperty( 1607 constants::pimServiceName, i_dbusObjPath, 1608 constants::inventoryItemInf, "Present"); 1609 1610 if (auto l_value = std::get_if<bool>(&l_presentPropValue)) 1611 { 1612 if (!(*l_value)) 1613 { 1614 throw std::runtime_error("Given FRU is not present"); 1615 } 1616 else 1617 { 1618 if (jsonUtility::isActionRequired(m_parsedJson, l_fruPath, 1619 "preAction", "deletion")) 1620 { 1621 if (!processPreAction(l_fruPath, "deletion")) 1622 { 1623 throw std::runtime_error("Pre action failed"); 1624 } 1625 } 1626 1627 std::vector<std::string> l_interfaceList{ 1628 constants::operationalStatusInf}; 1629 1630 types::MapperGetSubTree l_subTreeMap = 1631 dbusUtility::getObjectSubTree(i_dbusObjPath, 0, 1632 l_interfaceList); 1633 1634 types::ObjectMap l_objectMap; 1635 1636 // Updates VPD specific interfaces property value under PIM for 1637 // sub FRUs. 1638 for (const auto& [l_objectPath, l_serviceInterfaceMap] : 1639 l_subTreeMap) 1640 { 1641 types::InterfaceMap l_interfaceMap; 1642 vpdSpecificUtility::resetDataUnderPIM(l_objectPath, 1643 l_interfaceMap); 1644 l_objectMap.emplace(l_objectPath, 1645 std::move(l_interfaceMap)); 1646 } 1647 1648 types::InterfaceMap l_interfaceMap; 1649 vpdSpecificUtility::resetDataUnderPIM(i_dbusObjPath, 1650 l_interfaceMap); 1651 1652 l_objectMap.emplace(i_dbusObjPath, std::move(l_interfaceMap)); 1653 1654 if (!dbusUtility::callPIM(std::move(l_objectMap))) 1655 { 1656 throw std::runtime_error("Call to PIM failed."); 1657 } 1658 1659 if (jsonUtility::isActionRequired(m_parsedJson, l_fruPath, 1660 "postAction", "deletion")) 1661 { 1662 if (!processPostAction(l_fruPath, "deletion")) 1663 { 1664 throw std::runtime_error("Post action failed"); 1665 } 1666 } 1667 } 1668 } 1669 else 1670 { 1671 logging::logMessage( 1672 "Can't process delete VPD for FRU [" + i_dbusObjPath + 1673 "] as unable to read present property"); 1674 return; 1675 } 1676 1677 logging::logMessage( 1678 "Successfully completed deletion of FRU VPD for " + i_dbusObjPath); 1679 } 1680 catch (const std::exception& l_ex) 1681 { 1682 if (jsonUtility::isActionRequired(m_parsedJson, l_fruPath, 1683 "postFailAction", "deletion")) 1684 { 1685 if (!jsonUtility::executePostFailAction(m_parsedJson, l_fruPath, 1686 "deletion")) 1687 { 1688 logging::logMessage( 1689 "Post fail action failed for: " + i_dbusObjPath); 1690 } 1691 } 1692 1693 logging::logMessage("Failed to delete VPD for FRU : " + i_dbusObjPath + 1694 " error: " + std::string(l_ex.what())); 1695 } 1696 } 1697 1698 void Worker::setPresentProperty(const std::string& i_vpdPath, 1699 const bool& i_value) 1700 { 1701 try 1702 { 1703 if (i_vpdPath.empty()) 1704 { 1705 throw std::runtime_error( 1706 "Path is empty. Can't set present property"); 1707 } 1708 1709 types::ObjectMap l_objectInterfaceMap; 1710 1711 // If the given path is EEPROM path. 1712 if (m_parsedJson["frus"].contains(i_vpdPath)) 1713 { 1714 for (const auto& l_Fru : m_parsedJson["frus"][i_vpdPath]) 1715 { 1716 sdbusplus::message::object_path l_fruObjectPath( 1717 l_Fru["inventoryPath"]); 1718 1719 types::PropertyMap l_propertyValueMap; 1720 l_propertyValueMap.emplace("Present", i_value); 1721 1722 types::InterfaceMap l_interfaces; 1723 vpdSpecificUtility::insertOrMerge(l_interfaces, 1724 constants::inventoryItemInf, 1725 move(l_propertyValueMap)); 1726 1727 l_objectInterfaceMap.emplace(std::move(l_fruObjectPath), 1728 std::move(l_interfaces)); 1729 } 1730 } 1731 else 1732 { 1733 // consider it as an inventory path. 1734 if (i_vpdPath.find(constants::pimPath) != constants::VALUE_0) 1735 { 1736 throw std::runtime_error( 1737 "Invalid inventory path: " + i_vpdPath); 1738 } 1739 1740 types::PropertyMap l_propertyValueMap; 1741 l_propertyValueMap.emplace("Present", i_value); 1742 1743 types::InterfaceMap l_interfaces; 1744 vpdSpecificUtility::insertOrMerge(l_interfaces, 1745 constants::inventoryItemInf, 1746 move(l_propertyValueMap)); 1747 1748 l_objectInterfaceMap.emplace(i_vpdPath, std::move(l_interfaces)); 1749 } 1750 1751 // Notify PIM 1752 if (!dbusUtility::callPIM(move(l_objectInterfaceMap))) 1753 { 1754 throw DbusException( 1755 std::string(__FUNCTION__) + 1756 "Call to PIM failed while setting present property for path " + 1757 i_vpdPath); 1758 } 1759 } 1760 catch (const std::exception& l_ex) 1761 { 1762 EventLogger::createSyncPel( 1763 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 1764 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), 1765 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1766 } 1767 } 1768 1769 void Worker::performVpdRecollection() 1770 { 1771 try 1772 { 1773 // Check if system config JSON is present 1774 if (m_parsedJson.empty()) 1775 { 1776 throw std::runtime_error( 1777 "System config json object is empty, can't process recollection."); 1778 } 1779 1780 const auto& l_frusReplaceableAtStandby = 1781 jsonUtility::getListOfFrusReplaceableAtStandby(m_parsedJson); 1782 1783 for (const auto& l_fruInventoryPath : l_frusReplaceableAtStandby) 1784 { 1785 // ToDo: Add some logic/trace to know the flow to 1786 // collectSingleFruVpd has been directed via 1787 // performVpdRecollection. 1788 collectSingleFruVpd(l_fruInventoryPath); 1789 } 1790 return; 1791 } 1792 1793 catch (const std::exception& l_ex) 1794 { 1795 // TODO Log PEL 1796 logging::logMessage( 1797 "VPD recollection failed with error: " + std::string(l_ex.what())); 1798 } 1799 } 1800 1801 void Worker::collectSingleFruVpd( 1802 const sdbusplus::message::object_path& i_dbusObjPath) 1803 { 1804 try 1805 { 1806 // Check if system config JSON is present 1807 if (m_parsedJson.empty()) 1808 { 1809 logging::logMessage( 1810 "System config JSON object not present. Single FRU VPD collection is not performed for " + 1811 std::string(i_dbusObjPath)); 1812 return; 1813 } 1814 1815 // Get FRU path for the given D-bus object path from JSON 1816 const std::string& l_fruPath = 1817 jsonUtility::getFruPathFromJson(m_parsedJson, i_dbusObjPath); 1818 1819 if (l_fruPath.empty()) 1820 { 1821 logging::logMessage( 1822 "D-bus object path not present in JSON. Single FRU VPD collection is not performed for " + 1823 std::string(i_dbusObjPath)); 1824 return; 1825 } 1826 1827 // Check if host is up and running 1828 if (dbusUtility::isHostRunning()) 1829 { 1830 if (!jsonUtility::isFruReplaceableAtRuntime(m_parsedJson, 1831 l_fruPath)) 1832 { 1833 logging::logMessage( 1834 "Given FRU is not replaceable at host runtime. Single FRU VPD collection is not performed for " + 1835 std::string(i_dbusObjPath)); 1836 return; 1837 } 1838 } 1839 else if (dbusUtility::isBMCReady()) 1840 { 1841 if (!jsonUtility::isFruReplaceableAtStandby(m_parsedJson, 1842 l_fruPath) && 1843 (!jsonUtility::isFruReplaceableAtRuntime(m_parsedJson, 1844 l_fruPath))) 1845 { 1846 logging::logMessage( 1847 "Given FRU is neither replaceable at standby nor replaceable at runtime. Single FRU VPD collection is not performed for " + 1848 std::string(i_dbusObjPath)); 1849 return; 1850 } 1851 } 1852 1853 // Set CollectionStatus as InProgress. Since it's an intermediate state 1854 // D-bus set-property call is good enough to update the status. 1855 const std::string& l_collStatusProp = "CollectionStatus"; 1856 1857 if (!dbusUtility::writeDbusProperty( 1858 jsonUtility::getServiceName(m_parsedJson, 1859 std::string(i_dbusObjPath)), 1860 std::string(i_dbusObjPath), constants::vpdCollectionInterface, 1861 l_collStatusProp, 1862 types::DbusVariantType{constants::vpdCollectionInProgress})) 1863 { 1864 logging::logMessage( 1865 "Unable to set CollectionStatus as InProgress for " + 1866 std::string(i_dbusObjPath) + 1867 ". Continue single FRU VPD collection."); 1868 } 1869 1870 // Parse VPD 1871 types::VPDMapVariant l_parsedVpd = parseVpdFile(l_fruPath); 1872 1873 // If l_parsedVpd is pointing to std::monostate 1874 if (l_parsedVpd.index() == 0) 1875 { 1876 throw std::runtime_error( 1877 "VPD parsing failed for " + std::string(i_dbusObjPath)); 1878 } 1879 1880 // Get D-bus object map from worker class 1881 types::ObjectMap l_dbusObjectMap; 1882 populateDbus(l_parsedVpd, l_dbusObjectMap, l_fruPath); 1883 1884 if (l_dbusObjectMap.empty()) 1885 { 1886 throw std::runtime_error( 1887 "Failed to create D-bus object map. Single FRU VPD collection failed for " + 1888 std::string(i_dbusObjPath)); 1889 } 1890 1891 // Call PIM's Notify method 1892 if (!dbusUtility::callPIM(move(l_dbusObjectMap))) 1893 { 1894 throw std::runtime_error( 1895 "Notify PIM failed. Single FRU VPD collection failed for " + 1896 std::string(i_dbusObjPath)); 1897 } 1898 } 1899 catch (const std::exception& l_error) 1900 { 1901 // Notify FRU's VPD CollectionStatus as Failure 1902 if (!dbusUtility::notifyFRUCollectionStatus( 1903 std::string(i_dbusObjPath), constants::vpdCollectionFailure)) 1904 { 1905 logging::logMessage( 1906 "Call to PIM Notify method failed to update Collection status as Failure for " + 1907 std::string(i_dbusObjPath)); 1908 } 1909 1910 // TODO: Log PEL 1911 logging::logMessage(std::string(l_error.what())); 1912 } 1913 } 1914 1915 void Worker::setCollectionStatusProperty( 1916 const std::string& i_vpdPath, const std::string& i_value) const noexcept 1917 { 1918 try 1919 { 1920 if (i_vpdPath.empty()) 1921 { 1922 throw std::runtime_error( 1923 "Given path is empty. Can't set CollectionStatus property"); 1924 } 1925 1926 types::ObjectMap l_objectInterfaceMap; 1927 1928 if (m_parsedJson["frus"].contains(i_vpdPath)) 1929 { 1930 for (const auto& l_Fru : m_parsedJson["frus"][i_vpdPath]) 1931 { 1932 sdbusplus::message::object_path l_fruObjectPath( 1933 l_Fru["inventoryPath"]); 1934 1935 types::PropertyMap l_propertyValueMap; 1936 l_propertyValueMap.emplace("CollectionStatus", i_value); 1937 1938 types::InterfaceMap l_interfaces; 1939 vpdSpecificUtility::insertOrMerge( 1940 l_interfaces, constants::vpdCollectionInterface, 1941 move(l_propertyValueMap)); 1942 1943 l_objectInterfaceMap.emplace(std::move(l_fruObjectPath), 1944 std::move(l_interfaces)); 1945 } 1946 } 1947 else 1948 { 1949 // consider it as an inventory path. 1950 if (i_vpdPath.find(constants::pimPath) != constants::VALUE_0) 1951 { 1952 throw std::runtime_error( 1953 "Invalid inventory path: " + i_vpdPath + 1954 ". Can't set CollectionStatus property"); 1955 } 1956 1957 types::PropertyMap l_propertyValueMap; 1958 l_propertyValueMap.emplace("CollectionStatus", i_value); 1959 1960 types::InterfaceMap l_interfaces; 1961 vpdSpecificUtility::insertOrMerge(l_interfaces, 1962 constants::vpdCollectionInterface, 1963 move(l_propertyValueMap)); 1964 1965 l_objectInterfaceMap.emplace(i_vpdPath, std::move(l_interfaces)); 1966 } 1967 1968 // Notify PIM 1969 if (!dbusUtility::callPIM(move(l_objectInterfaceMap))) 1970 { 1971 throw DbusException( 1972 std::string(__FUNCTION__) + 1973 "Call to PIM failed while setting CollectionStatus property for path " + 1974 i_vpdPath); 1975 } 1976 } 1977 catch (const std::exception& l_ex) 1978 { 1979 EventLogger::createSyncPel( 1980 EventLogger::getErrorType(l_ex), types::SeverityType::Warning, 1981 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex), 1982 std::nullopt, std::nullopt, std::nullopt, std::nullopt); 1983 } 1984 } 1985 } // namespace vpd 1986