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