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