xref: /openbmc/openpower-vpd-parser/vpd-manager/src/manager.cpp (revision 565e1140c9fbefa79b4fdd9fa16f24adbf30602c)
1 #include "config.h"
2 
3 #include "manager.hpp"
4 
5 #include "backup_restore.hpp"
6 #include "constants.hpp"
7 #include "exceptions.hpp"
8 #include "logger.hpp"
9 #include "parser.hpp"
10 #include "parser_factory.hpp"
11 #include "parser_interface.hpp"
12 #include "single_fab.hpp"
13 #include "types.hpp"
14 #include "utility/dbus_utility.hpp"
15 #include "utility/json_utility.hpp"
16 #include "utility/vpd_specific_utility.hpp"
17 
18 #include <boost/asio/steady_timer.hpp>
19 #include <sdbusplus/bus/match.hpp>
20 #include <sdbusplus/message.hpp>
21 
22 namespace vpd
23 {
24 Manager::Manager(
25     const std::shared_ptr<boost::asio::io_context>& ioCon,
26     const std::shared_ptr<sdbusplus::asio::dbus_interface>& iFace,
27     const std::shared_ptr<sdbusplus::asio::connection>& asioConnection) :
28     m_ioContext(ioCon), m_interface(iFace), m_asioConnection(asioConnection)
29 {
30 #ifdef IBM_SYSTEM
31     if (!dbusUtility::isChassisPowerOn())
32     {
33         SingleFab l_singleFab;
34         const int& l_rc = l_singleFab.singleFabImOverride();
35 
36         if (l_rc == constants::FAILURE)
37         {
38             throw std::runtime_error(
39                 std::string(__FUNCTION__) +
40                 " : Found an invalid system configuration. Needs manual intervention. BMC is being quiesced.");
41         }
42     }
43 #endif
44 
45     try
46     {
47 #ifdef IBM_SYSTEM
48         if (dbusUtility::isChassisPowerOn())
49         {
50             // At power on, less number of FRU(s) needs collection. we can scale
51             // down the threads to reduce CPU utilization.
52             m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT,
53                                                 constants::VALUE_1);
54         }
55         else
56         {
57             // Initialize with default configuration
58             m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT);
59         }
60 
61         // Set up minimal things that is needed before bus name is claimed.
62         m_worker->performInitialSetup();
63 
64         // set callback to detect any asset tag change
65         registerAssetTagChangeCallback();
66 
67         // set async timer to detect if system VPD is published on D-Bus.
68         SetTimerToDetectSVPDOnDbus();
69 
70         // set async timer to detect if VPD collection is done.
71         SetTimerToDetectVpdCollectionStatus();
72 
73         // Instantiate GpioMonitor class
74         m_gpioMonitor = std::make_shared<GpioMonitor>(
75             m_worker->getSysCfgJsonObj(), m_worker, m_ioContext);
76 
77 #endif
78         // set callback to detect host state change.
79         registerHostStateChangeCallback();
80 
81         // For backward compatibility. Should be depricated.
82         iFace->register_method(
83             "WriteKeyword",
84             [this](const sdbusplus::message::object_path i_path,
85                    const std::string i_recordName, const std::string i_keyword,
86                    const types::BinaryVector i_value) -> int {
87                 return this->updateKeyword(
88                     i_path, std::make_tuple(i_recordName, i_keyword, i_value));
89             });
90 
91         // Register methods under com.ibm.VPD.Manager interface
92         iFace->register_method(
93             "UpdateKeyword",
94             [this](const types::Path i_vpdPath,
95                    const types::WriteVpdParams i_paramsToWriteData) -> int {
96                 return this->updateKeyword(i_vpdPath, i_paramsToWriteData);
97             });
98 
99         iFace->register_method(
100             "WriteKeywordOnHardware",
101             [this](const types::Path i_fruPath,
102                    const types::WriteVpdParams i_paramsToWriteData) -> int {
103                 return this->updateKeywordOnHardware(i_fruPath,
104                                                      i_paramsToWriteData);
105             });
106 
107         iFace->register_method(
108             "ReadKeyword",
109             [this](const types::Path i_fruPath,
110                    const types::ReadVpdParams i_paramsToReadData)
111                 -> types::DbusVariantType {
112                 return this->readKeyword(i_fruPath, i_paramsToReadData);
113             });
114 
115         iFace->register_method(
116             "CollectFRUVPD",
117             [this](const sdbusplus::message::object_path& i_dbusObjPath) {
118                 this->collectSingleFruVpd(i_dbusObjPath);
119             });
120 
121         iFace->register_method(
122             "deleteFRUVPD",
123             [this](const sdbusplus::message::object_path& i_dbusObjPath) {
124                 this->deleteSingleFruVpd(i_dbusObjPath);
125             });
126 
127         iFace->register_method(
128             "GetExpandedLocationCode",
129             [this](const std::string& i_unexpandedLocationCode,
130                    uint16_t& i_nodeNumber) -> std::string {
131                 return this->getExpandedLocationCode(i_unexpandedLocationCode,
132                                                      i_nodeNumber);
133             });
134 
135         iFace->register_method("GetFRUsByExpandedLocationCode",
136                                [this](const std::string& i_expandedLocationCode)
137                                    -> types::ListOfPaths {
138                                    return this->getFrusByExpandedLocationCode(
139                                        i_expandedLocationCode);
140                                });
141 
142         iFace->register_method(
143             "GetFRUsByUnexpandedLocationCode",
144             [this](const std::string& i_unexpandedLocationCode,
145                    uint16_t& i_nodeNumber) -> types::ListOfPaths {
146                 return this->getFrusByUnexpandedLocationCode(
147                     i_unexpandedLocationCode, i_nodeNumber);
148             });
149 
150         iFace->register_method(
151             "GetHardwarePath",
152             [this](const sdbusplus::message::object_path& i_dbusObjPath)
153                 -> std::string { return this->getHwPath(i_dbusObjPath); });
154 
155         iFace->register_method("PerformVPDRecollection", [this]() {
156             this->performVpdRecollection();
157         });
158 
159         // Indicates FRU VPD collection for the system has not started.
160         iFace->register_property_rw<std::string>(
161             "CollectionStatus", sdbusplus::vtable::property_::emits_change,
162             [this](const std::string l_currStatus, const auto&) {
163                 m_vpdCollectionStatus = l_currStatus;
164                 return 0;
165             },
166             [this](const auto&) { return m_vpdCollectionStatus; });
167     }
168     catch (const std::exception& e)
169     {
170         logging::logMessage(
171             "Manager class instantiation failed. " + std::string(e.what()));
172 
173         vpd::EventLogger::createSyncPel(
174             vpd::EventLogger::getErrorType(e), vpd::types::SeverityType::Error,
175             __FILE__, __FUNCTION__, 0, vpd::EventLogger::getErrorMsg(e),
176             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
177     }
178 }
179 
180 #ifdef IBM_SYSTEM
181 void Manager::registerAssetTagChangeCallback()
182 {
183     static std::shared_ptr<sdbusplus::bus::match_t> l_assetMatch =
184         std::make_shared<sdbusplus::bus::match_t>(
185             *m_asioConnection,
186             sdbusplus::bus::match::rules::propertiesChanged(
187                 constants::systemInvPath, constants::assetTagInf),
188             [this](sdbusplus::message_t& l_msg) {
189                 processAssetTagChangeCallback(l_msg);
190             });
191 }
192 
193 void Manager::processAssetTagChangeCallback(sdbusplus::message_t& i_msg)
194 {
195     try
196     {
197         if (i_msg.is_method_error())
198         {
199             throw std::runtime_error(
200                 "Error reading callback msg for asset tag.");
201         }
202 
203         std::string l_objectPath;
204         types::PropertyMap l_propMap;
205         i_msg.read(l_objectPath, l_propMap);
206 
207         const auto& l_itrToAssetTag = l_propMap.find("AssetTag");
208         if (l_itrToAssetTag != l_propMap.end())
209         {
210             if (auto l_assetTag =
211                     std::get_if<std::string>(&(l_itrToAssetTag->second)))
212             {
213                 // Call Notify to persist the AssetTag
214                 types::ObjectMap l_objectMap = {
215                     {sdbusplus::message::object_path(constants::systemInvPath),
216                      {{constants::assetTagInf, {{"AssetTag", *l_assetTag}}}}}};
217 
218                 // Notify PIM
219                 if (!dbusUtility::callPIM(move(l_objectMap)))
220                 {
221                     throw std::runtime_error(
222                         "Call to PIM failed for asset tag update.");
223                 }
224             }
225         }
226         else
227         {
228             throw std::runtime_error(
229                 "Could not find asset tag in callback message.");
230         }
231     }
232     catch (const std::exception& l_ex)
233     {
234         // TODO: Log PEL with below description.
235         logging::logMessage("Asset tag callback update failed with error: " +
236                             std::string(l_ex.what()));
237     }
238 }
239 
240 void Manager::SetTimerToDetectSVPDOnDbus()
241 {
242     try
243     {
244         static boost::asio::steady_timer timer(*m_ioContext);
245 
246         // timer for 2 seconds
247         auto asyncCancelled = timer.expires_after(std::chrono::seconds(2));
248 
249         (asyncCancelled == 0) ? logging::logMessage("Timer started")
250                               : logging::logMessage("Timer re-started");
251 
252         timer.async_wait([this](const boost::system::error_code& ec) {
253             if (ec == boost::asio::error::operation_aborted)
254             {
255                 throw std::runtime_error(
256                     std::string(__FUNCTION__) +
257                     ": Timer to detect system VPD collection status was aborted.");
258             }
259 
260             if (ec)
261             {
262                 throw std::runtime_error(
263                     std::string(__FUNCTION__) +
264                     ": Timer to detect System VPD collection failed");
265             }
266 
267             if (m_worker->isSystemVPDOnDBus())
268             {
269                 // cancel the timer
270                 timer.cancel();
271 
272                 // Triggering FRU VPD collection. Setting status to "In
273                 // Progress".
274                 m_interface->set_property("CollectionStatus",
275                                           std::string("InProgress"));
276                 m_worker->collectFrusFromJson();
277             }
278         });
279     }
280     catch (const std::exception& l_ex)
281     {
282         EventLogger::createAsyncPel(
283             EventLogger::getErrorType(l_ex), types::SeverityType::Critical,
284             __FILE__, __FUNCTION__, 0,
285             std::string("Collection for FRUs failed with reason:") +
286                 EventLogger::getErrorMsg(l_ex),
287             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
288     }
289 }
290 
291 void Manager::SetTimerToDetectVpdCollectionStatus()
292 {
293     // Keeping max retry for 2 minutes. TODO: Make it configurable based on
294     // system type.
295     static constexpr auto MAX_RETRY = 12;
296 
297     static boost::asio::steady_timer l_timer(*m_ioContext);
298     static uint8_t l_timerRetry = 0;
299 
300     auto l_asyncCancelled = l_timer.expires_after(std::chrono::seconds(10));
301 
302     (l_asyncCancelled == 0)
303         ? logging::logMessage("Collection Timer started")
304         : logging::logMessage("Collection Timer re-started");
305 
306     l_timer.async_wait([this](const boost::system::error_code& ec) {
307         if (ec == boost::asio::error::operation_aborted)
308         {
309             throw std::runtime_error(
310                 "Timer to detect thread collection status was aborted");
311         }
312 
313         if (ec)
314         {
315             throw std::runtime_error(
316                 "Timer to detect thread collection failed");
317         }
318 
319         if (m_worker->isAllFruCollectionDone())
320         {
321             // cancel the timer
322             l_timer.cancel();
323             processFailedEeproms();
324 
325             // update VPD for powerVS system.
326             ConfigurePowerVsSystem();
327 
328             m_interface->set_property("CollectionStatus",
329                                       std::string("Completed"));
330 
331             const nlohmann::json& l_sysCfgJsonObj =
332                 m_worker->getSysCfgJsonObj();
333             if (jsonUtility::isBackupAndRestoreRequired(l_sysCfgJsonObj))
334             {
335                 BackupAndRestore l_backupAndRestoreObj(l_sysCfgJsonObj);
336                 l_backupAndRestoreObj.backupAndRestore();
337             }
338         }
339         else
340         {
341             auto l_threadCount = m_worker->getActiveThreadCount();
342             if (l_timerRetry == MAX_RETRY)
343             {
344                 l_timer.cancel();
345                 logging::logMessage("Taking too long. Active thread = " +
346                                     std::to_string(l_threadCount));
347             }
348             else
349             {
350                 l_timerRetry++;
351                 logging::logMessage("Collection is in progress for [" +
352                                     std::to_string(l_threadCount) + "] FRUs.");
353 
354                 SetTimerToDetectVpdCollectionStatus();
355             }
356         }
357     });
358 }
359 
360 void Manager::checkAndUpdatePowerVsVpd(
361     const nlohmann::json& i_powerVsJsonObj,
362     std::vector<std::string>& o_failedPathList)
363 {
364     for (const auto& [l_fruPath, l_recJson] : i_powerVsJsonObj.items())
365     {
366         nlohmann::json l_sysCfgJsonObj{};
367         if (m_worker.get() != nullptr)
368         {
369             l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
370         }
371 
372         // The utility method will handle emty JSON case. No explicit
373         // handling required here.
374         auto l_inventoryPath = jsonUtility::getInventoryObjPathFromJson(
375             l_sysCfgJsonObj, l_fruPath);
376 
377         // Mark it as failed if inventory path not found in JSON.
378         if (l_inventoryPath.empty())
379         {
380             o_failedPathList.push_back(l_fruPath);
381             continue;
382         }
383 
384         // check if the FRU is present
385         if (!dbusUtility::isInventoryPresent(l_inventoryPath))
386         {
387             logging::logMessage(
388                 "Inventory not present, skip updating part number. Path: " +
389                 l_inventoryPath);
390             continue;
391         }
392 
393         // check if the FRU needs CCIN check before updating PN.
394         if (l_recJson.contains("CCIN"))
395         {
396             const auto& l_ccinFromDbus =
397                 vpdSpecificUtility::getCcinFromDbus(l_inventoryPath);
398 
399             // Not an ideal situation as CCIN can't be empty.
400             if (l_ccinFromDbus.empty())
401             {
402                 o_failedPathList.push_back(l_fruPath);
403                 continue;
404             }
405 
406             std::vector<std::string> l_ccinListFromJson = l_recJson["CCIN"];
407 
408             if (find(l_ccinListFromJson.begin(), l_ccinListFromJson.end(),
409                      l_ccinFromDbus) == l_ccinListFromJson.end())
410             {
411                 // Don't update PN in this case.
412                 continue;
413             }
414         }
415 
416         for (const auto& [l_recordName, l_kwdJson] : l_recJson.items())
417         {
418             // Record name can't be CCIN, skip processing as it is there for PN
419             // update based on CCIN check.
420             if (l_recordName == constants::kwdCCIN)
421             {
422                 continue;
423             }
424 
425             for (const auto& [l_kwdName, l_kwdValue] : l_kwdJson.items())
426             {
427                 // Is value of type array.
428                 if (!l_kwdValue.is_array())
429                 {
430                     o_failedPathList.push_back(l_fruPath);
431                     continue;
432                 }
433 
434                 // Get current FRU Part number.
435                 auto l_retVal = dbusUtility::readDbusProperty(
436                     constants::pimServiceName, l_inventoryPath,
437                     constants::viniInf, constants::kwdFN);
438 
439                 auto l_ptrToFn = std::get_if<types::BinaryVector>(&l_retVal);
440 
441                 if (!l_ptrToFn)
442                 {
443                     o_failedPathList.push_back(l_fruPath);
444                     continue;
445                 }
446 
447                 types::BinaryVector l_binaryKwdValue =
448                     l_kwdValue.get<types::BinaryVector>();
449                 if (l_binaryKwdValue == (*l_ptrToFn))
450                 {
451                     continue;
452                 }
453 
454                 // Update part number only if required.
455                 if (updateKeyword(
456                         l_fruPath,
457                         std::make_tuple(l_recordName, l_kwdName, l_kwdValue)) ==
458                     constants::FAILURE)
459                 {
460                     o_failedPathList.push_back(l_fruPath);
461                     continue;
462                 }
463 
464                 // update the Asset interface Spare part number explicitly.
465                 if (!dbusUtility::callPIM(types::ObjectMap{
466                         {l_inventoryPath,
467                          {{constants::assetInf,
468                            {{"SparePartNumber",
469                              std::string(l_binaryKwdValue.begin(),
470                                          l_binaryKwdValue.end())}}}}}}))
471                 {
472                     logging::logMessage(
473                         "Updating Spare Part Number under Asset interface failed for path [" +
474                         l_inventoryPath + "]");
475                 }
476 
477                 // Just needed for logging.
478                 std::string l_initialPartNum((*l_ptrToFn).begin(),
479                                              (*l_ptrToFn).end());
480                 std::string l_finalPartNum(l_binaryKwdValue.begin(),
481                                            l_binaryKwdValue.end());
482                 logging::logMessage(
483                     "FRU Part number updated for path [" + l_inventoryPath +
484                     "]" + "From [" + l_initialPartNum + "]" + " to [" +
485                     l_finalPartNum + "]");
486             }
487         }
488     }
489 }
490 
491 void Manager::ConfigurePowerVsSystem()
492 {
493     std::vector<std::string> l_failedPathList;
494     try
495     {
496         types::BinaryVector l_imValue = dbusUtility::getImFromDbus();
497         if (l_imValue.empty())
498         {
499             throw DbusException("Invalid IM value read from Dbus");
500         }
501 
502         if (!vpdSpecificUtility::isPowerVsConfiguration(l_imValue))
503         {
504             // TODO: Should booting be blocked in case of some
505             // misconfigurations?
506             return;
507         }
508 
509         const nlohmann::json& l_powerVsJsonObj =
510             jsonUtility::getPowerVsJson(l_imValue);
511 
512         if (l_powerVsJsonObj.empty())
513         {
514             throw std::runtime_error("PowerVS Json not found");
515         }
516 
517         checkAndUpdatePowerVsVpd(l_powerVsJsonObj, l_failedPathList);
518 
519         if (!l_failedPathList.empty())
520         {
521             throw std::runtime_error(
522                 "Part number update failed for following paths: ");
523         }
524     }
525     catch (const std::exception& l_ex)
526     {
527         // TODO log appropriate PEL
528     }
529 }
530 
531 void Manager::processFailedEeproms()
532 {
533     if (m_worker.get() != nullptr)
534     {
535         // TODO:
536         // - iterate through list of EEPROMs for which thread creation has
537         // failed
538         // - For each failed EEPROM, trigger VPD collection
539         m_worker->getFailedEepromPaths().clear();
540     }
541 }
542 #endif
543 
544 int Manager::updateKeyword(const types::Path i_vpdPath,
545                            const types::WriteVpdParams i_paramsToWriteData)
546 {
547     if (i_vpdPath.empty())
548     {
549         logging::logMessage("Given VPD path is empty.");
550         return -1;
551     }
552 
553     types::Path l_fruPath;
554     nlohmann::json l_sysCfgJsonObj{};
555 
556     if (m_worker.get() != nullptr)
557     {
558         l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
559 
560         // Get the EEPROM path
561         if (!l_sysCfgJsonObj.empty())
562         {
563             l_fruPath =
564                 jsonUtility::getFruPathFromJson(l_sysCfgJsonObj, i_vpdPath);
565         }
566     }
567 
568     if (l_fruPath.empty())
569     {
570         l_fruPath = i_vpdPath;
571     }
572 
573     try
574     {
575         std::shared_ptr<Parser> l_parserObj =
576             std::make_shared<Parser>(l_fruPath, l_sysCfgJsonObj);
577         return l_parserObj->updateVpdKeyword(i_paramsToWriteData);
578     }
579     catch (const std::exception& l_exception)
580     {
581         // TODO:: error log needed
582         logging::logMessage("Update keyword failed for file[" + i_vpdPath +
583                             "], reason: " + std::string(l_exception.what()));
584         return -1;
585     }
586 }
587 
588 int Manager::updateKeywordOnHardware(
589     const types::Path i_fruPath,
590     const types::WriteVpdParams i_paramsToWriteData) noexcept
591 {
592     try
593     {
594         if (i_fruPath.empty())
595         {
596             throw std::runtime_error("Given FRU path is empty");
597         }
598 
599         nlohmann::json l_sysCfgJsonObj{};
600 
601         if (m_worker.get() != nullptr)
602         {
603             l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
604         }
605 
606         std::shared_ptr<Parser> l_parserObj =
607             std::make_shared<Parser>(i_fruPath, l_sysCfgJsonObj);
608         return l_parserObj->updateVpdKeywordOnHardware(i_paramsToWriteData);
609     }
610     catch (const std::exception& l_exception)
611     {
612         EventLogger::createAsyncPel(
613             types::ErrorType::InvalidEeprom, types::SeverityType::Informational,
614             __FILE__, __FUNCTION__, 0,
615             "Update keyword on hardware failed for file[" + i_fruPath +
616                 "], reason: " + std::string(l_exception.what()),
617             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
618 
619         return constants::FAILURE;
620     }
621 }
622 
623 types::DbusVariantType Manager::readKeyword(
624     const types::Path i_fruPath, const types::ReadVpdParams i_paramsToReadData)
625 {
626     try
627     {
628         nlohmann::json l_jsonObj{};
629 
630         if (m_worker.get() != nullptr)
631         {
632             l_jsonObj = m_worker->getSysCfgJsonObj();
633         }
634 
635         std::error_code ec;
636 
637         // Check if given path is filesystem path
638         if (!std::filesystem::exists(i_fruPath, ec) && (ec))
639         {
640             throw std::runtime_error(
641                 "Given file path " + i_fruPath + " not found.");
642         }
643 
644         logging::logMessage("Performing VPD read on " + i_fruPath);
645 
646         std::shared_ptr<vpd::Parser> l_parserObj =
647             std::make_shared<vpd::Parser>(i_fruPath, l_jsonObj);
648 
649         std::shared_ptr<vpd::ParserInterface> l_vpdParserInstance =
650             l_parserObj->getVpdParserInstance();
651 
652         return (
653             l_vpdParserInstance->readKeywordFromHardware(i_paramsToReadData));
654     }
655     catch (const std::exception& e)
656     {
657         logging::logMessage(
658             e.what() + std::string(". VPD manager read operation failed for ") +
659             i_fruPath);
660         throw types::DeviceError::ReadFailure();
661     }
662 }
663 
664 void Manager::collectSingleFruVpd(
665     const sdbusplus::message::object_path& i_dbusObjPath)
666 {
667     try
668     {
669         if (m_vpdCollectionStatus != "Completed")
670         {
671             logging::logMessage(
672                 "Currently VPD CollectionStatus is not completed. Cannot perform single FRU VPD collection for " +
673                 std::string(i_dbusObjPath));
674             return;
675         }
676 
677         // Get system config JSON object from worker class
678         nlohmann::json l_sysCfgJsonObj{};
679 
680         if (m_worker.get() != nullptr)
681         {
682             l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
683         }
684 
685         // Check if system config JSON is present
686         if (l_sysCfgJsonObj.empty())
687         {
688             logging::logMessage(
689                 "System config JSON object not present. Single FRU VPD collection is not performed for " +
690                 std::string(i_dbusObjPath));
691             return;
692         }
693 
694         // Get FRU path for the given D-bus object path from JSON
695         const std::string& l_fruPath =
696             jsonUtility::getFruPathFromJson(l_sysCfgJsonObj, i_dbusObjPath);
697 
698         if (l_fruPath.empty())
699         {
700             logging::logMessage(
701                 "D-bus object path not present in JSON. Single FRU VPD collection is not performed for " +
702                 std::string(i_dbusObjPath));
703             return;
704         }
705 
706         // Check if host is up and running
707         if (dbusUtility::isHostRunning())
708         {
709             if (!jsonUtility::isFruReplaceableAtRuntime(l_sysCfgJsonObj,
710                                                         l_fruPath))
711             {
712                 logging::logMessage(
713                     "Given FRU is not replaceable at host runtime. Single FRU VPD collection is not performed for " +
714                     std::string(i_dbusObjPath));
715                 return;
716             }
717         }
718         else if (dbusUtility::isBMCReady())
719         {
720             if (!jsonUtility::isFruReplaceableAtStandby(l_sysCfgJsonObj,
721                                                         l_fruPath) &&
722                 (!jsonUtility::isFruReplaceableAtRuntime(l_sysCfgJsonObj,
723                                                          l_fruPath)))
724             {
725                 logging::logMessage(
726                     "Given FRU is neither replaceable at standby nor replaceable at runtime. Single FRU VPD collection is not performed for " +
727                     std::string(i_dbusObjPath));
728                 return;
729             }
730         }
731 
732         // Set CollectionStatus as InProgress. Since it's an intermediate state
733         // D-bus set-property call is good enough to update the status.
734         const std::string& l_collStatusProp = "CollectionStatus";
735 
736         if (!dbusUtility::writeDbusProperty(
737                 jsonUtility::getServiceName(l_sysCfgJsonObj,
738                                             std::string(i_dbusObjPath)),
739                 std::string(i_dbusObjPath), constants::vpdCollectionInterface,
740                 l_collStatusProp,
741                 types::DbusVariantType{constants::vpdCollectionInProgress}))
742         {
743             logging::logMessage(
744                 "Unable to set CollectionStatus as InProgress for " +
745                 std::string(i_dbusObjPath) +
746                 ". Continue single FRU VPD collection.");
747         }
748 
749         // Parse VPD
750         types::VPDMapVariant l_parsedVpd = m_worker->parseVpdFile(l_fruPath);
751 
752         // If l_parsedVpd is pointing to std::monostate
753         if (l_parsedVpd.index() == 0)
754         {
755             throw std::runtime_error(
756                 "VPD parsing failed for " + std::string(i_dbusObjPath));
757         }
758 
759         // Get D-bus object map from worker class
760         types::ObjectMap l_dbusObjectMap;
761         m_worker->populateDbus(l_parsedVpd, l_dbusObjectMap, l_fruPath);
762 
763         if (l_dbusObjectMap.empty())
764         {
765             throw std::runtime_error(
766                 "Failed to create D-bus object map. Single FRU VPD collection failed for " +
767                 std::string(i_dbusObjPath));
768         }
769 
770         // Call PIM's Notify method
771         if (!dbusUtility::callPIM(move(l_dbusObjectMap)))
772         {
773             throw std::runtime_error(
774                 "Notify PIM failed. Single FRU VPD collection failed for " +
775                 std::string(i_dbusObjPath));
776         }
777     }
778     catch (const std::exception& l_error)
779     {
780         // Notify FRU's VPD CollectionStatus as Failure
781         if (!dbusUtility::notifyFRUCollectionStatus(
782                 std::string(i_dbusObjPath), constants::vpdCollectionFailure))
783         {
784             logging::logMessage(
785                 "Call to PIM Notify method failed to update Collection status as Failure for " +
786                 std::string(i_dbusObjPath));
787         }
788 
789         // TODO: Log PEL
790         logging::logMessage(std::string(l_error.what()));
791     }
792 }
793 
794 void Manager::deleteSingleFruVpd(
795     const sdbusplus::message::object_path& i_dbusObjPath)
796 {
797     try
798     {
799         if (std::string(i_dbusObjPath).empty())
800         {
801             throw std::runtime_error(
802                 "Given DBus object path is empty. Aborting FRU VPD deletion.");
803         }
804 
805         if (m_worker.get() == nullptr)
806         {
807             throw std::runtime_error(
808                 "Worker object not found, can't perform FRU VPD deletion for: " +
809                 std::string(i_dbusObjPath));
810         }
811 
812         m_worker->deleteFruVpd(std::string(i_dbusObjPath));
813     }
814     catch (const std::exception& l_ex)
815     {
816         // TODO: Log PEL
817         logging::logMessage(l_ex.what());
818     }
819 }
820 
821 bool Manager::isValidUnexpandedLocationCode(
822     const std::string& i_unexpandedLocationCode)
823 {
824     if ((i_unexpandedLocationCode.length() <
825          constants::UNEXP_LOCATION_CODE_MIN_LENGTH) ||
826         ((i_unexpandedLocationCode.compare(0, 4, "Ufcs") !=
827           constants::STR_CMP_SUCCESS) &&
828          (i_unexpandedLocationCode.compare(0, 4, "Umts") !=
829           constants::STR_CMP_SUCCESS)) ||
830         ((i_unexpandedLocationCode.length() >
831           constants::UNEXP_LOCATION_CODE_MIN_LENGTH) &&
832          (i_unexpandedLocationCode.find("-") != 4)))
833     {
834         return false;
835     }
836 
837     return true;
838 }
839 
840 std::string Manager::getExpandedLocationCode(
841     const std::string& i_unexpandedLocationCode,
842     [[maybe_unused]] const uint16_t i_nodeNumber)
843 {
844     if (!isValidUnexpandedLocationCode(i_unexpandedLocationCode))
845     {
846         phosphor::logging::elog<types::DbusInvalidArgument>(
847             types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
848             types::InvalidArgument::ARGUMENT_VALUE(
849                 i_unexpandedLocationCode.c_str()));
850     }
851 
852     const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
853     if (!l_sysCfgJsonObj.contains("frus"))
854     {
855         logging::logMessage("Missing frus tag in system config JSON");
856     }
857 
858     const nlohmann::json& l_listOfFrus =
859         l_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>();
860 
861     for (const auto& l_frus : l_listOfFrus.items())
862     {
863         for (const auto& l_aFru : l_frus.value())
864         {
865             if (l_aFru["extraInterfaces"].contains(
866                     constants::locationCodeInf) &&
867                 l_aFru["extraInterfaces"][constants::locationCodeInf].value(
868                     "LocationCode", "") == i_unexpandedLocationCode)
869             {
870                 return std::get<std::string>(dbusUtility::readDbusProperty(
871                     l_aFru["serviceName"], l_aFru["inventoryPath"],
872                     constants::locationCodeInf, "LocationCode"));
873             }
874         }
875     }
876     phosphor::logging::elog<types::DbusInvalidArgument>(
877         types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
878         types::InvalidArgument::ARGUMENT_VALUE(
879             i_unexpandedLocationCode.c_str()));
880 }
881 
882 types::ListOfPaths Manager::getFrusByUnexpandedLocationCode(
883     const std::string& i_unexpandedLocationCode,
884     [[maybe_unused]] const uint16_t i_nodeNumber)
885 {
886     types::ListOfPaths l_inventoryPaths;
887 
888     if (!isValidUnexpandedLocationCode(i_unexpandedLocationCode))
889     {
890         phosphor::logging::elog<types::DbusInvalidArgument>(
891             types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
892             types::InvalidArgument::ARGUMENT_VALUE(
893                 i_unexpandedLocationCode.c_str()));
894     }
895 
896     const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
897     if (!l_sysCfgJsonObj.contains("frus"))
898     {
899         logging::logMessage("Missing frus tag in system config JSON");
900     }
901 
902     const nlohmann::json& l_listOfFrus =
903         l_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>();
904 
905     for (const auto& l_frus : l_listOfFrus.items())
906     {
907         for (const auto& l_aFru : l_frus.value())
908         {
909             if (l_aFru["extraInterfaces"].contains(
910                     constants::locationCodeInf) &&
911                 l_aFru["extraInterfaces"][constants::locationCodeInf].value(
912                     "LocationCode", "") == i_unexpandedLocationCode)
913             {
914                 l_inventoryPaths.push_back(
915                     l_aFru.at("inventoryPath")
916                         .get_ref<const nlohmann::json::string_t&>());
917             }
918         }
919     }
920 
921     if (l_inventoryPaths.empty())
922     {
923         phosphor::logging::elog<types::DbusInvalidArgument>(
924             types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
925             types::InvalidArgument::ARGUMENT_VALUE(
926                 i_unexpandedLocationCode.c_str()));
927     }
928 
929     return l_inventoryPaths;
930 }
931 
932 std::string Manager::getHwPath(
933     const sdbusplus::message::object_path& i_dbusObjPath)
934 {
935     // Dummy code to supress unused variable warning. To be removed.
936     logging::logMessage(std::string(i_dbusObjPath));
937 
938     return std::string{};
939 }
940 
941 std::tuple<std::string, uint16_t> Manager::getUnexpandedLocationCode(
942     const std::string& i_expandedLocationCode)
943 {
944     /**
945      * Location code should always start with U and fulfil minimum length
946      * criteria.
947      */
948     if (i_expandedLocationCode[0] != 'U' ||
949         i_expandedLocationCode.length() <
950             constants::EXP_LOCATION_CODE_MIN_LENGTH)
951     {
952         phosphor::logging::elog<types::DbusInvalidArgument>(
953             types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
954             types::InvalidArgument::ARGUMENT_VALUE(
955                 i_expandedLocationCode.c_str()));
956     }
957 
958     std::string l_fcKwd;
959 
960     auto l_fcKwdValue = dbusUtility::readDbusProperty(
961         "xyz.openbmc_project.Inventory.Manager",
962         "/xyz/openbmc_project/inventory/system/chassis/motherboard",
963         "com.ibm.ipzvpd.VCEN", "FC");
964 
965     if (auto l_kwdValue = std::get_if<types::BinaryVector>(&l_fcKwdValue))
966     {
967         l_fcKwd.assign(l_kwdValue->begin(), l_kwdValue->end());
968     }
969 
970     // Get the first part of expanded location code to check for FC or TM.
971     std::string l_firstKwd = i_expandedLocationCode.substr(1, 4);
972 
973     std::string l_unexpandedLocationCode{};
974     uint16_t l_nodeNummber = constants::INVALID_NODE_NUMBER;
975 
976     // Check if this value matches the value of FC keyword.
977     if (l_fcKwd.substr(0, 4) == l_firstKwd)
978     {
979         /**
980          * Period(.) should be there in expanded location code to seggregate
981          * FC, node number and SE values.
982          */
983         size_t l_nodeStartPos = i_expandedLocationCode.find('.');
984         if (l_nodeStartPos == std::string::npos)
985         {
986             phosphor::logging::elog<types::DbusInvalidArgument>(
987                 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
988                 types::InvalidArgument::ARGUMENT_VALUE(
989                     i_expandedLocationCode.c_str()));
990         }
991 
992         size_t l_nodeEndPos =
993             i_expandedLocationCode.find('.', l_nodeStartPos + 1);
994         if (l_nodeEndPos == std::string::npos)
995         {
996             phosphor::logging::elog<types::DbusInvalidArgument>(
997                 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
998                 types::InvalidArgument::ARGUMENT_VALUE(
999                     i_expandedLocationCode.c_str()));
1000         }
1001 
1002         // Skip 3 bytes for '.ND'
1003         l_nodeNummber = std::stoi(i_expandedLocationCode.substr(
1004             l_nodeStartPos + 3, (l_nodeEndPos - l_nodeStartPos - 3)));
1005 
1006         /**
1007          * Confirm if there are other details apart FC, node number and SE
1008          * in location code
1009          */
1010         if (i_expandedLocationCode.length() >
1011             constants::EXP_LOCATION_CODE_MIN_LENGTH)
1012         {
1013             l_unexpandedLocationCode =
1014                 i_expandedLocationCode[0] + std::string("fcs") +
1015                 i_expandedLocationCode.substr(
1016                     l_nodeEndPos + 1 + constants::SE_KWD_LENGTH,
1017                     std::string::npos);
1018         }
1019         else
1020         {
1021             l_unexpandedLocationCode = "Ufcs";
1022         }
1023     }
1024     else
1025     {
1026         std::string l_tmKwd;
1027         // Read TM keyword value.
1028         auto l_tmKwdValue = dbusUtility::readDbusProperty(
1029             "xyz.openbmc_project.Inventory.Manager",
1030             "/xyz/openbmc_project/inventory/system/chassis/motherboard",
1031             "com.ibm.ipzvpd.VSYS", "TM");
1032 
1033         if (auto l_kwdValue = std::get_if<types::BinaryVector>(&l_tmKwdValue))
1034         {
1035             l_tmKwd.assign(l_kwdValue->begin(), l_kwdValue->end());
1036         }
1037 
1038         // Check if the substr matches to TM keyword value.
1039         if (l_tmKwd.substr(0, 4) == l_firstKwd)
1040         {
1041             /**
1042              * System location code will not have node number and any other
1043              * details.
1044              */
1045             l_unexpandedLocationCode = "Umts";
1046         }
1047         // The given location code is neither "fcs" or "mts".
1048         else
1049         {
1050             phosphor::logging::elog<types::DbusInvalidArgument>(
1051                 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
1052                 types::InvalidArgument::ARGUMENT_VALUE(
1053                     i_expandedLocationCode.c_str()));
1054         }
1055     }
1056 
1057     return std::make_tuple(l_unexpandedLocationCode, l_nodeNummber);
1058 }
1059 
1060 types::ListOfPaths Manager::getFrusByExpandedLocationCode(
1061     const std::string& i_expandedLocationCode)
1062 {
1063     std::tuple<std::string, uint16_t> l_locationAndNodePair =
1064         getUnexpandedLocationCode(i_expandedLocationCode);
1065 
1066     return getFrusByUnexpandedLocationCode(std::get<0>(l_locationAndNodePair),
1067                                            std::get<1>(l_locationAndNodePair));
1068 }
1069 
1070 void Manager::registerHostStateChangeCallback()
1071 {
1072     static std::shared_ptr<sdbusplus::bus::match_t> l_hostState =
1073         std::make_shared<sdbusplus::bus::match_t>(
1074             *m_asioConnection,
1075             sdbusplus::bus::match::rules::propertiesChanged(
1076                 constants::hostObjectPath, constants::hostInterface),
1077             [this](sdbusplus::message_t& i_msg) {
1078                 hostStateChangeCallBack(i_msg);
1079             });
1080 }
1081 
1082 void Manager::hostStateChangeCallBack(sdbusplus::message_t& i_msg)
1083 {
1084     try
1085     {
1086         if (i_msg.is_method_error())
1087         {
1088             throw std::runtime_error(
1089                 "Error reading callback message for host state");
1090         }
1091 
1092         std::string l_objectPath;
1093         types::PropertyMap l_propMap;
1094         i_msg.read(l_objectPath, l_propMap);
1095 
1096         const auto l_itr = l_propMap.find("CurrentHostState");
1097 
1098         if (l_itr == l_propMap.end())
1099         {
1100             throw std::runtime_error(
1101                 "CurrentHostState field is missing in callback message");
1102         }
1103 
1104         if (auto l_hostState = std::get_if<std::string>(&(l_itr->second)))
1105         {
1106             // implies system is moving from standby to power on state
1107             if (*l_hostState == "xyz.openbmc_project.State.Host.HostState."
1108                                 "TransitioningToRunning")
1109             {
1110                 // TODO: check for all the essential FRUs in the system.
1111 
1112                 // Perform recollection.
1113                 performVpdRecollection();
1114                 return;
1115             }
1116         }
1117         else
1118         {
1119             throw std::runtime_error(
1120                 "Invalid type recieved in variant for host state.");
1121         }
1122     }
1123     catch (const std::exception& l_ex)
1124     {
1125         // TODO: Log PEL.
1126         logging::logMessage(l_ex.what());
1127     }
1128 }
1129 
1130 void Manager::performVpdRecollection()
1131 {
1132     try
1133     {
1134         if (m_worker.get() != nullptr)
1135         {
1136             nlohmann::json l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
1137 
1138             // Check if system config JSON is present
1139             if (l_sysCfgJsonObj.empty())
1140             {
1141                 throw std::runtime_error(
1142                     "System config json object is empty, can't process recollection.");
1143             }
1144 
1145             const auto& l_frusReplaceableAtStandby =
1146                 jsonUtility::getListOfFrusReplaceableAtStandby(l_sysCfgJsonObj);
1147 
1148             for (const auto& l_fruInventoryPath : l_frusReplaceableAtStandby)
1149             {
1150                 // ToDo: Add some logic/trace to know the flow to
1151                 // collectSingleFruVpd has been directed via
1152                 // performVpdRecollection.
1153                 collectSingleFruVpd(l_fruInventoryPath);
1154             }
1155             return;
1156         }
1157 
1158         throw std::runtime_error(
1159             "Worker object not found can't process recollection");
1160     }
1161     catch (const std::exception& l_ex)
1162     {
1163         // TODO Log PEL
1164         logging::logMessage(
1165             "VPD recollection failed with error: " + std::string(l_ex.what()));
1166     }
1167 }
1168 } // namespace vpd
1169