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