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