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