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