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