xref: /openbmc/openpower-vpd-parser/vpd-manager/oem-handler/ibm_handler.cpp (revision 393c0fade4690d37e7dd7227730b2bcaef9214db)
1 #include "config.h"
2 
3 #include "ibm_handler.hpp"
4 
5 #include "parser.hpp"
6 
7 #include <utility/common_utility.hpp>
8 #include <utility/dbus_utility.hpp>
9 #include <utility/json_utility.hpp>
10 #include <utility/vpd_specific_utility.hpp>
11 
12 namespace vpd
13 {
14 IbmHandler::IbmHandler(
15     std::shared_ptr<Worker>& o_worker,
16     std::shared_ptr<BackupAndRestore>& o_backupAndRestoreObj,
17     const std::shared_ptr<sdbusplus::asio::dbus_interface>& i_iFace,
18     const std::shared_ptr<boost::asio::io_context>& i_ioCon,
19     const std::shared_ptr<sdbusplus::asio::connection>& i_asioConnection) :
20     m_worker(o_worker), m_backupAndRestoreObj(o_backupAndRestoreObj),
21     m_interface(i_iFace), m_ioContext(i_ioCon),
22     m_asioConnection(i_asioConnection)
23 {
24     if (dbusUtility::isChassisPowerOn())
25     {
26         // At power on, less number of FRU(s) needs collection. we can scale
27         // down the threads to reduce CPU utilization.
28         m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT,
29                                             constants::VALUE_1);
30     }
31     else
32     {
33         // Initialize with default configuration
34         m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT);
35     }
36 
37     // Set up minimal things that is needed before bus name is claimed.
38     performInitialSetup();
39 
40     if (!m_sysCfgJsonObj.empty() &&
41         jsonUtility::isBackupAndRestoreRequired(m_sysCfgJsonObj))
42     {
43         try
44         {
45             m_backupAndRestoreObj =
46                 std::make_shared<BackupAndRestore>(m_sysCfgJsonObj);
47         }
48         catch (const std::exception& l_ex)
49         {
50             logging::logMessage("Back up and restore instantiation failed. {" +
51                                 std::string(l_ex.what()) + "}");
52 
53             EventLogger::createSyncPel(
54                 EventLogger::getErrorType(l_ex), types::SeverityType::Warning,
55                 __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex),
56                 std::nullopt, std::nullopt, std::nullopt, std::nullopt);
57         }
58     }
59 
60     // callback to detect host state change.
61     registerHostStateChangeCallback();
62 
63     // set callback to detect any asset tag change
64     registerAssetTagChangeCallback();
65 
66     // set async timer to detect if system VPD is published on D-Bus.
67     SetTimerToDetectSVPDOnDbus();
68 
69     // set async timer to detect if VPD collection is done.
70     SetTimerToDetectVpdCollectionStatus();
71 
72     // Instantiate GpioMonitor class
73     m_gpioMonitor =
74         std::make_shared<GpioMonitor>(m_sysCfgJsonObj, m_worker, m_ioContext);
75 }
76 
77 void IbmHandler::registerAssetTagChangeCallback()
78 {
79     static std::shared_ptr<sdbusplus::bus::match_t> l_assetMatch =
80         std::make_shared<sdbusplus::bus::match_t>(
81             *m_asioConnection,
82             sdbusplus::bus::match::rules::propertiesChanged(
83                 constants::systemInvPath, constants::assetTagInf),
84             [this](sdbusplus::message_t& l_msg) {
85                 processAssetTagChangeCallback(l_msg);
86             });
87 }
88 
89 void IbmHandler::processAssetTagChangeCallback(sdbusplus::message_t& i_msg)
90 {
91     try
92     {
93         if (i_msg.is_method_error())
94         {
95             throw std::runtime_error(
96                 "Error reading callback msg for asset tag.");
97         }
98 
99         std::string l_objectPath;
100         types::PropertyMap l_propMap;
101         i_msg.read(l_objectPath, l_propMap);
102 
103         const auto& l_itrToAssetTag = l_propMap.find("AssetTag");
104         if (l_itrToAssetTag != l_propMap.end())
105         {
106             if (auto l_assetTag =
107                     std::get_if<std::string>(&(l_itrToAssetTag->second)))
108             {
109                 // Call Notify to persist the AssetTag
110                 types::ObjectMap l_objectMap = {
111                     {sdbusplus::message::object_path(constants::systemInvPath),
112                      {{constants::assetTagInf, {{"AssetTag", *l_assetTag}}}}}};
113 
114                 // Notify PIM
115                 if (!dbusUtility::callPIM(move(l_objectMap)))
116                 {
117                     throw std::runtime_error(
118                         "Call to PIM failed for asset tag update.");
119                 }
120             }
121         }
122         else
123         {
124             throw std::runtime_error(
125                 "Could not find asset tag in callback message.");
126         }
127     }
128     catch (const std::exception& l_ex)
129     {
130         // TODO: Log PEL with below description.
131         logging::logMessage("Asset tag callback update failed with error: " +
132                             std::string(l_ex.what()));
133     }
134 }
135 
136 void IbmHandler::SetTimerToDetectSVPDOnDbus()
137 {
138     try
139     {
140         static boost::asio::steady_timer timer(*m_ioContext);
141 
142         // timer for 2 seconds
143         auto asyncCancelled = timer.expires_after(std::chrono::seconds(2));
144 
145         (asyncCancelled == 0) ? logging::logMessage("Timer started")
146                               : logging::logMessage("Timer re-started");
147 
148         timer.async_wait([this](const boost::system::error_code& ec) {
149             if (ec == boost::asio::error::operation_aborted)
150             {
151                 throw std::runtime_error(
152                     std::string(__FUNCTION__) +
153                     ": Timer to detect system VPD collection status was aborted.");
154             }
155 
156             if (ec)
157             {
158                 throw std::runtime_error(
159                     std::string(__FUNCTION__) +
160                     ": Timer to detect System VPD collection failed");
161             }
162 
163             if (m_worker->isSystemVPDOnDBus())
164             {
165                 // cancel the timer
166                 timer.cancel();
167 
168                 // Triggering FRU VPD collection. Setting status to "In
169                 // Progress".
170                 m_interface->set_property("CollectionStatus",
171                                           std::string("InProgress"));
172                 m_worker->collectFrusFromJson();
173             }
174         });
175     }
176     catch (const std::exception& l_ex)
177     {
178         EventLogger::createAsyncPel(
179             EventLogger::getErrorType(l_ex), types::SeverityType::Critical,
180             __FILE__, __FUNCTION__, 0,
181             std::string("Collection for FRUs failed with reason:") +
182                 EventLogger::getErrorMsg(l_ex),
183             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
184     }
185 }
186 
187 void IbmHandler::SetTimerToDetectVpdCollectionStatus()
188 {
189     // Keeping max retry for 2 minutes. TODO: Make it configurable based on
190     // system type.
191     static constexpr auto MAX_RETRY = 12;
192 
193     static boost::asio::steady_timer l_timer(*m_ioContext);
194     static uint8_t l_timerRetry = 0;
195 
196     auto l_asyncCancelled = l_timer.expires_after(std::chrono::seconds(10));
197 
198     (l_asyncCancelled == 0)
199         ? logging::logMessage("Collection Timer started")
200         : logging::logMessage("Collection Timer re-started");
201 
202     l_timer.async_wait([this](const boost::system::error_code& ec) {
203         if (ec == boost::asio::error::operation_aborted)
204         {
205             throw std::runtime_error(
206                 "Timer to detect thread collection status was aborted");
207         }
208 
209         if (ec)
210         {
211             throw std::runtime_error(
212                 "Timer to detect thread collection failed");
213         }
214 
215         if (m_worker->isAllFruCollectionDone())
216         {
217             // cancel the timer
218             l_timer.cancel();
219             processFailedEeproms();
220 
221             // update VPD for powerVS system.
222             ConfigurePowerVsSystem();
223 
224             std::cout << "m_worker->isSystemVPDOnDBus() completed" << std::endl;
225             m_interface->set_property("CollectionStatus",
226                                       std::string("Completed"));
227 
228             if (m_backupAndRestoreObj)
229             {
230                 m_backupAndRestoreObj->backupAndRestore();
231             }
232         }
233         else
234         {
235             auto l_threadCount = m_worker->getActiveThreadCount();
236             if (l_timerRetry == MAX_RETRY)
237             {
238                 l_timer.cancel();
239                 logging::logMessage("Taking too long. Active thread = " +
240                                     std::to_string(l_threadCount));
241             }
242             else
243             {
244                 l_timerRetry++;
245                 logging::logMessage("Collection is in progress for [" +
246                                     std::to_string(l_threadCount) + "] FRUs.");
247 
248                 SetTimerToDetectVpdCollectionStatus();
249             }
250         }
251     });
252 }
253 
254 void IbmHandler::checkAndUpdatePowerVsVpd(
255     const nlohmann::json& i_powerVsJsonObj,
256     std::vector<std::string>& o_failedPathList)
257 {
258     for (const auto& [l_fruPath, l_recJson] : i_powerVsJsonObj.items())
259     {
260         nlohmann::json l_sysCfgJsonObj{};
261         if (m_worker.get() != nullptr)
262         {
263             l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
264         }
265 
266         // The utility method will handle emty JSON case. No explicit
267         // handling required here.
268         auto l_inventoryPath = jsonUtility::getInventoryObjPathFromJson(
269             l_sysCfgJsonObj, l_fruPath);
270 
271         // Mark it as failed if inventory path not found in JSON.
272         if (l_inventoryPath.empty())
273         {
274             o_failedPathList.push_back(l_fruPath);
275             continue;
276         }
277 
278         // check if the FRU is present
279         if (!dbusUtility::isInventoryPresent(l_inventoryPath))
280         {
281             logging::logMessage(
282                 "Inventory not present, skip updating part number. Path: " +
283                 l_inventoryPath);
284             continue;
285         }
286 
287         // check if the FRU needs CCIN check before updating PN.
288         if (l_recJson.contains("CCIN"))
289         {
290             const auto& l_ccinFromDbus =
291                 vpdSpecificUtility::getCcinFromDbus(l_inventoryPath);
292 
293             // Not an ideal situation as CCIN can't be empty.
294             if (l_ccinFromDbus.empty())
295             {
296                 o_failedPathList.push_back(l_fruPath);
297                 continue;
298             }
299 
300             std::vector<std::string> l_ccinListFromJson = l_recJson["CCIN"];
301 
302             if (find(l_ccinListFromJson.begin(), l_ccinListFromJson.end(),
303                      l_ccinFromDbus) == l_ccinListFromJson.end())
304             {
305                 // Don't update PN in this case.
306                 continue;
307             }
308         }
309 
310         for (const auto& [l_recordName, l_kwdJson] : l_recJson.items())
311         {
312             // Record name can't be CCIN, skip processing as it is there for PN
313             // update based on CCIN check.
314             if (l_recordName == constants::kwdCCIN)
315             {
316                 continue;
317             }
318 
319             for (const auto& [l_kwdName, l_kwdValue] : l_kwdJson.items())
320             {
321                 // Is value of type array.
322                 if (!l_kwdValue.is_array())
323                 {
324                     o_failedPathList.push_back(l_fruPath);
325                     continue;
326                 }
327 
328                 // Get current FRU Part number.
329                 auto l_retVal = dbusUtility::readDbusProperty(
330                     constants::pimServiceName, l_inventoryPath,
331                     constants::viniInf, constants::kwdFN);
332 
333                 auto l_ptrToFn = std::get_if<types::BinaryVector>(&l_retVal);
334 
335                 if (!l_ptrToFn)
336                 {
337                     o_failedPathList.push_back(l_fruPath);
338                     continue;
339                 }
340 
341                 types::BinaryVector l_binaryKwdValue =
342                     l_kwdValue.get<types::BinaryVector>();
343                 if (l_binaryKwdValue == (*l_ptrToFn))
344                 {
345                     continue;
346                 }
347 
348                 // Update part number only if required.
349                 std::shared_ptr<Parser> l_parserObj =
350                     std::make_shared<Parser>(l_fruPath, l_sysCfgJsonObj);
351                 if (l_parserObj->updateVpdKeyword(std::make_tuple(
352                         l_recordName, l_kwdName, l_binaryKwdValue)) ==
353                     constants::FAILURE)
354                 {
355                     o_failedPathList.push_back(l_fruPath);
356                     continue;
357                 }
358 
359                 // update the Asset interface Spare part number explicitly.
360                 if (!dbusUtility::callPIM(types::ObjectMap{
361                         {l_inventoryPath,
362                          {{constants::assetInf,
363                            {{"SparePartNumber",
364                              std::string(l_binaryKwdValue.begin(),
365                                          l_binaryKwdValue.end())}}}}}}))
366                 {
367                     logging::logMessage(
368                         "Updating Spare Part Number under Asset interface failed for path [" +
369                         l_inventoryPath + "]");
370                 }
371 
372                 // Just needed for logging.
373                 std::string l_initialPartNum((*l_ptrToFn).begin(),
374                                              (*l_ptrToFn).end());
375                 std::string l_finalPartNum(l_binaryKwdValue.begin(),
376                                            l_binaryKwdValue.end());
377                 logging::logMessage(
378                     "FRU Part number updated for path [" + l_inventoryPath +
379                     "]" + "From [" + l_initialPartNum + "]" + " to [" +
380                     l_finalPartNum + "]");
381             }
382         }
383     }
384 }
385 
386 void IbmHandler::ConfigurePowerVsSystem()
387 {
388     std::vector<std::string> l_failedPathList;
389     try
390     {
391         types::BinaryVector l_imValue = dbusUtility::getImFromDbus();
392         if (l_imValue.empty())
393         {
394             throw DbusException("Invalid IM value read from Dbus");
395         }
396 
397         if (!vpdSpecificUtility::isPowerVsConfiguration(l_imValue))
398         {
399             // TODO: Should booting be blocked in case of some
400             // misconfigurations?
401             return;
402         }
403 
404         const nlohmann::json& l_powerVsJsonObj =
405             jsonUtility::getPowerVsJson(l_imValue);
406 
407         if (l_powerVsJsonObj.empty())
408         {
409             throw std::runtime_error("PowerVS Json not found");
410         }
411 
412         checkAndUpdatePowerVsVpd(l_powerVsJsonObj, l_failedPathList);
413 
414         if (!l_failedPathList.empty())
415         {
416             throw std::runtime_error(
417                 "Part number update failed for following paths: ");
418         }
419     }
420     catch (const std::exception& l_ex)
421     {
422         // TODO log appropriate PEL
423     }
424 }
425 
426 void IbmHandler::processFailedEeproms()
427 {
428     if (m_worker.get() != nullptr)
429     {
430         // TODO:
431         // - iterate through list of EEPROMs for which thread creation has
432         // failed
433         // - For each failed EEPROM, trigger VPD collection
434         m_worker->getFailedEepromPaths().clear();
435     }
436 }
437 
438 void IbmHandler::registerHostStateChangeCallback()
439 {
440     static std::shared_ptr<sdbusplus::bus::match_t> l_hostState =
441         std::make_shared<sdbusplus::bus::match_t>(
442             *m_asioConnection,
443             sdbusplus::bus::match::rules::propertiesChanged(
444                 constants::hostObjectPath, constants::hostInterface),
445             [this](sdbusplus::message_t& i_msg) {
446                 hostStateChangeCallBack(i_msg);
447             });
448 }
449 
450 void IbmHandler::hostStateChangeCallBack(sdbusplus::message_t& i_msg)
451 {
452     try
453     {
454         if (i_msg.is_method_error())
455         {
456             throw std::runtime_error(
457                 "Error reading callback message for host state");
458         }
459 
460         std::string l_objectPath;
461         types::PropertyMap l_propMap;
462         i_msg.read(l_objectPath, l_propMap);
463 
464         const auto l_itr = l_propMap.find("CurrentHostState");
465 
466         if (l_itr == l_propMap.end())
467         {
468             throw std::runtime_error(
469                 "CurrentHostState field is missing in callback message");
470         }
471 
472         if (auto l_hostState = std::get_if<std::string>(&(l_itr->second)))
473         {
474             // implies system is moving from standby to power on state
475             if (*l_hostState == "xyz.openbmc_project.State.Host.HostState."
476                                 "TransitioningToRunning")
477             {
478                 // TODO: check for all the essential FRUs in the system.
479 
480                 if (m_worker.get() != nullptr)
481                 {
482                     // Perform recollection.
483                     m_worker->performVpdRecollection();
484                 }
485                 else
486                 {
487                     logging::logMessage(
488                         "Failed to get worker object, Abort re-collection");
489                 }
490             }
491         }
492         else
493         {
494             throw std::runtime_error(
495                 "Invalid type recieved in variant for host state.");
496         }
497     }
498     catch (const std::exception& l_ex)
499     {
500         // TODO: Log PEL.
501         logging::logMessage(l_ex.what());
502     }
503 }
504 
505 void IbmHandler::primeSystemBlueprint()
506 {
507     if (m_sysCfgJsonObj.empty())
508     {
509         return;
510     }
511 
512     const nlohmann::json& l_listOfFrus =
513         m_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>();
514 
515     for (const auto& l_itemFRUS : l_listOfFrus.items())
516     {
517         const std::string& l_vpdFilePath = l_itemFRUS.key();
518 
519         if (l_vpdFilePath == SYSTEM_VPD_FILE_PATH)
520         {
521             continue;
522         }
523 
524         // Prime the inventry for FRUs which
525         // are not present/processing had some error.
526         if (m_worker.get() != nullptr &&
527             !m_worker->primeInventory(l_vpdFilePath))
528         {
529             logging::logMessage(
530                 "Priming of inventory failed for FRU " + l_vpdFilePath);
531         }
532     }
533 }
534 
535 void IbmHandler::enableMuxChips()
536 {
537     if (m_sysCfgJsonObj.empty())
538     {
539         // config JSON should not be empty at this point of execution.
540         throw std::runtime_error("Config JSON is empty. Can't enable muxes");
541         return;
542     }
543 
544     if (!m_sysCfgJsonObj.contains("muxes"))
545     {
546         logging::logMessage("No mux defined for the system in config JSON");
547         return;
548     }
549 
550     // iterate over each MUX detail and enable them.
551     for (const auto& item : m_sysCfgJsonObj["muxes"])
552     {
553         if (item.contains("holdidlepath"))
554         {
555             std::string cmd = "echo 0 > ";
556             cmd += item["holdidlepath"];
557 
558             logging::logMessage("Enabling mux with command = " + cmd);
559 
560             commonUtility::executeCmd(cmd);
561             continue;
562         }
563 
564         logging::logMessage(
565             "Mux Entry does not have hold idle path. Can't enable the mux");
566     }
567 }
568 
569 void IbmHandler::performInitialSetup()
570 {
571     try
572     {
573         if (!dbusUtility::isChassisPowerOn())
574         {
575             logging::logMessage("Chassis is in Off state.");
576             if (m_worker.get() != nullptr)
577             {
578                 m_worker->setDeviceTreeAndJson();
579                 // Get the system config JSON object.
580                 m_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
581             }
582             else
583             {
584                 throw std::runtime_error(
585                     "Worker object not found. Can't set up device tree and Json.");
586             }
587             primeSystemBlueprint();
588         }
589         else
590         {
591             // get the JSON from worker.
592             m_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
593         }
594 
595         // Enable all mux which are used for connecting to the i2c on the
596         // pcie slots for pcie cards. These are not enabled by kernel due to
597         // an issue seen with Castello cards, where the i2c line hangs on a
598         // probe.
599         enableMuxChips();
600 
601         // Nothing needs to be done. Service restarted or BMC re-booted for
602         // some reason at system power on.
603         return;
604     }
605     catch (const std::exception& l_ex)
606     {
607         // Any issue in system's inital set up is handled in this catch. Error
608         // will not propogate to manager.
609         EventLogger::createSyncPel(
610             EventLogger::getErrorType(l_ex), types::SeverityType::Critical,
611             __FILE__, __FUNCTION__, 0, EventLogger::getErrorMsg(l_ex),
612             std::nullopt, std::nullopt, std::nullopt, std::nullopt);
613     }
614 }
615 
616 } // namespace vpd
617