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