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