1 #include "config.h"
2
3 #include "manager.hpp"
4
5 #include "backup_restore.hpp"
6 #include "constants.hpp"
7 #include "exceptions.hpp"
8 #include "logger.hpp"
9 #include "parser.hpp"
10 #include "parser_factory.hpp"
11 #include "parser_interface.hpp"
12 #include "types.hpp"
13 #include "utility/dbus_utility.hpp"
14 #include "utility/json_utility.hpp"
15 #include "utility/vpd_specific_utility.hpp"
16
17 #include <boost/asio/steady_timer.hpp>
18 #include <sdbusplus/bus/match.hpp>
19 #include <sdbusplus/message.hpp>
20
21 namespace vpd
22 {
Manager(const std::shared_ptr<boost::asio::io_context> & ioCon,const std::shared_ptr<sdbusplus::asio::dbus_interface> & iFace,const std::shared_ptr<sdbusplus::asio::connection> & asioConnection)23 Manager::Manager(
24 const std::shared_ptr<boost::asio::io_context>& ioCon,
25 const std::shared_ptr<sdbusplus::asio::dbus_interface>& iFace,
26 const std::shared_ptr<sdbusplus::asio::connection>& asioConnection) :
27 m_ioContext(ioCon), m_interface(iFace), m_asioConnection(asioConnection)
28 {
29 try
30 {
31 #ifdef IBM_SYSTEM
32 if (dbusUtility::isChassisPowerOn())
33 {
34 // At power on, less number of FRU(s) needs collection. we can scale
35 // down the threads to reduce CPU utilization.
36 m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT,
37 constants::VALUE_1);
38 }
39 else
40 {
41 // Initialize with default configuration
42 m_worker = std::make_shared<Worker>(INVENTORY_JSON_DEFAULT);
43 }
44
45 // Set up minimal things that is needed before bus name is claimed.
46 m_worker->performInitialSetup();
47
48 // set callback to detect any asset tag change
49 registerAssetTagChangeCallback();
50
51 // set async timer to detect if system VPD is published on D-Bus.
52 SetTimerToDetectSVPDOnDbus();
53
54 // set async timer to detect if VPD collection is done.
55 SetTimerToDetectVpdCollectionStatus();
56
57 // Instantiate GpioMonitor class
58 m_gpioMonitor = std::make_shared<GpioMonitor>(
59 m_worker->getSysCfgJsonObj(), m_worker, m_ioContext);
60
61 #endif
62 // set callback to detect host state change.
63 registerHostStateChangeCallback();
64
65 // For backward compatibility. Should be depricated.
66 iFace->register_method(
67 "WriteKeyword",
68 [this](const sdbusplus::message::object_path i_path,
69 const std::string i_recordName, const std::string i_keyword,
70 const types::BinaryVector i_value) -> int {
71 return this->updateKeyword(
72 i_path, std::make_tuple(i_recordName, i_keyword, i_value));
73 });
74
75 // Register methods under com.ibm.VPD.Manager interface
76 iFace->register_method(
77 "UpdateKeyword",
78 [this](const types::Path i_vpdPath,
79 const types::WriteVpdParams i_paramsToWriteData) -> int {
80 return this->updateKeyword(i_vpdPath, i_paramsToWriteData);
81 });
82
83 iFace->register_method(
84 "WriteKeywordOnHardware",
85 [this](const types::Path i_fruPath,
86 const types::WriteVpdParams i_paramsToWriteData) -> int {
87 return this->updateKeywordOnHardware(i_fruPath,
88 i_paramsToWriteData);
89 });
90
91 iFace->register_method(
92 "ReadKeyword",
93 [this](const types::Path i_fruPath,
94 const types::ReadVpdParams i_paramsToReadData)
95 -> types::DbusVariantType {
96 return this->readKeyword(i_fruPath, i_paramsToReadData);
97 });
98
99 iFace->register_method(
100 "CollectFRUVPD",
101 [this](const sdbusplus::message::object_path& i_dbusObjPath) {
102 this->collectSingleFruVpd(i_dbusObjPath);
103 });
104
105 iFace->register_method(
106 "deleteFRUVPD",
107 [this](const sdbusplus::message::object_path& i_dbusObjPath) {
108 this->deleteSingleFruVpd(i_dbusObjPath);
109 });
110
111 iFace->register_method(
112 "GetExpandedLocationCode",
113 [this](const std::string& i_unexpandedLocationCode,
114 uint16_t& i_nodeNumber) -> std::string {
115 return this->getExpandedLocationCode(i_unexpandedLocationCode,
116 i_nodeNumber);
117 });
118
119 iFace->register_method("GetFRUsByExpandedLocationCode",
120 [this](const std::string& i_expandedLocationCode)
121 -> types::ListOfPaths {
122 return this->getFrusByExpandedLocationCode(
123 i_expandedLocationCode);
124 });
125
126 iFace->register_method(
127 "GetFRUsByUnexpandedLocationCode",
128 [this](const std::string& i_unexpandedLocationCode,
129 uint16_t& i_nodeNumber) -> types::ListOfPaths {
130 return this->getFrusByUnexpandedLocationCode(
131 i_unexpandedLocationCode, i_nodeNumber);
132 });
133
134 iFace->register_method(
135 "GetHardwarePath",
136 [this](const sdbusplus::message::object_path& i_dbusObjPath)
137 -> std::string { return this->getHwPath(i_dbusObjPath); });
138
139 iFace->register_method("PerformVPDRecollection", [this]() {
140 this->performVpdRecollection();
141 });
142
143 // Indicates FRU VPD collection for the system has not started.
144 iFace->register_property_rw<std::string>(
145 "CollectionStatus", sdbusplus::vtable::property_::emits_change,
146 [this](const std::string l_currStatus, const auto&) {
147 m_vpdCollectionStatus = l_currStatus;
148 return 0;
149 },
150 [this](const auto&) { return m_vpdCollectionStatus; });
151 }
152 catch (const std::exception& e)
153 {
154 logging::logMessage(
155 "VPD-Manager service failed. " + std::string(e.what()));
156 throw;
157 }
158 }
159
160 #ifdef IBM_SYSTEM
registerAssetTagChangeCallback()161 void Manager::registerAssetTagChangeCallback()
162 {
163 static std::shared_ptr<sdbusplus::bus::match_t> l_assetMatch =
164 std::make_shared<sdbusplus::bus::match_t>(
165 *m_asioConnection,
166 sdbusplus::bus::match::rules::propertiesChanged(
167 constants::systemInvPath, constants::assetTagInf),
168 [this](sdbusplus::message_t& l_msg) {
169 processAssetTagChangeCallback(l_msg);
170 });
171 }
172
processAssetTagChangeCallback(sdbusplus::message_t & i_msg)173 void Manager::processAssetTagChangeCallback(sdbusplus::message_t& i_msg)
174 {
175 try
176 {
177 if (i_msg.is_method_error())
178 {
179 throw std::runtime_error(
180 "Error reading callback msg for asset tag.");
181 }
182
183 std::string l_objectPath;
184 types::PropertyMap l_propMap;
185 i_msg.read(l_objectPath, l_propMap);
186
187 const auto& l_itrToAssetTag = l_propMap.find("AssetTag");
188 if (l_itrToAssetTag != l_propMap.end())
189 {
190 if (auto l_assetTag =
191 std::get_if<std::string>(&(l_itrToAssetTag->second)))
192 {
193 // Call Notify to persist the AssetTag
194 types::ObjectMap l_objectMap = {
195 {sdbusplus::message::object_path(constants::systemInvPath),
196 {{constants::assetTagInf, {{"AssetTag", *l_assetTag}}}}}};
197
198 // Notify PIM
199 if (!dbusUtility::callPIM(move(l_objectMap)))
200 {
201 throw std::runtime_error(
202 "Call to PIM failed for asset tag update.");
203 }
204 }
205 }
206 else
207 {
208 throw std::runtime_error(
209 "Could not find asset tag in callback message.");
210 }
211 }
212 catch (const std::exception& l_ex)
213 {
214 // TODO: Log PEL with below description.
215 logging::logMessage("Asset tag callback update failed with error: " +
216 std::string(l_ex.what()));
217 }
218 }
219
SetTimerToDetectSVPDOnDbus()220 void Manager::SetTimerToDetectSVPDOnDbus()
221 {
222 try
223 {
224 static boost::asio::steady_timer timer(*m_ioContext);
225
226 // timer for 2 seconds
227 auto asyncCancelled = timer.expires_after(std::chrono::seconds(2));
228
229 (asyncCancelled == 0) ? logging::logMessage("Timer started")
230 : logging::logMessage("Timer re-started");
231
232 timer.async_wait([this](const boost::system::error_code& ec) {
233 if (ec == boost::asio::error::operation_aborted)
234 {
235 throw std::runtime_error(
236 std::string(__FUNCTION__) +
237 ": Timer to detect system VPD collection status was aborted.");
238 }
239
240 if (ec)
241 {
242 throw std::runtime_error(
243 std::string(__FUNCTION__) +
244 ": Timer to detect System VPD collection failed");
245 }
246
247 if (m_worker->isSystemVPDOnDBus())
248 {
249 // cancel the timer
250 timer.cancel();
251
252 // Triggering FRU VPD collection. Setting status to "In
253 // Progress".
254 m_interface->set_property("CollectionStatus",
255 std::string("InProgress"));
256 m_worker->collectFrusFromJson();
257 }
258 });
259 }
260 catch (const std::exception& l_ex)
261 {
262 EventLogger::createAsyncPel(
263 EventLogger::getErrorType(l_ex), types::SeverityType::Critical,
264 __FILE__, __FUNCTION__, 0,
265 std::string("Collection for FRUs failed with reason:") +
266 EventLogger::getErrorMsg(l_ex),
267 std::nullopt, std::nullopt, std::nullopt, std::nullopt);
268 }
269 }
270
SetTimerToDetectVpdCollectionStatus()271 void Manager::SetTimerToDetectVpdCollectionStatus()
272 {
273 // Keeping max retry for 2 minutes. TODO: Make it configurable based on
274 // system type.
275 static constexpr auto MAX_RETRY = 12;
276
277 static boost::asio::steady_timer l_timer(*m_ioContext);
278 static uint8_t l_timerRetry = 0;
279
280 auto l_asyncCancelled = l_timer.expires_after(std::chrono::seconds(10));
281
282 (l_asyncCancelled == 0)
283 ? logging::logMessage("Collection Timer started")
284 : logging::logMessage("Collection Timer re-started");
285
286 l_timer.async_wait([this](const boost::system::error_code& ec) {
287 if (ec == boost::asio::error::operation_aborted)
288 {
289 throw std::runtime_error(
290 "Timer to detect thread collection status was aborted");
291 }
292
293 if (ec)
294 {
295 throw std::runtime_error(
296 "Timer to detect thread collection failed");
297 }
298
299 if (m_worker->isAllFruCollectionDone())
300 {
301 // cancel the timer
302 l_timer.cancel();
303 processFailedEeproms();
304
305 // update VPD for powerVS system.
306 ConfigurePowerVsSystem();
307
308 m_interface->set_property("CollectionStatus",
309 std::string("Completed"));
310
311 const nlohmann::json& l_sysCfgJsonObj =
312 m_worker->getSysCfgJsonObj();
313 if (jsonUtility::isBackupAndRestoreRequired(l_sysCfgJsonObj))
314 {
315 BackupAndRestore l_backupAndRestoreObj(l_sysCfgJsonObj);
316 l_backupAndRestoreObj.backupAndRestore();
317 }
318 }
319 else
320 {
321 auto l_threadCount = m_worker->getActiveThreadCount();
322 if (l_timerRetry == MAX_RETRY)
323 {
324 l_timer.cancel();
325 logging::logMessage("Taking too long. Active thread = " +
326 std::to_string(l_threadCount));
327 }
328 else
329 {
330 l_timerRetry++;
331 logging::logMessage("Collection is in progress for [" +
332 std::to_string(l_threadCount) + "] FRUs.");
333
334 SetTimerToDetectVpdCollectionStatus();
335 }
336 }
337 });
338 }
339
checkAndUpdatePowerVsVpd(const nlohmann::json & i_powerVsJsonObj,std::vector<std::string> & o_failedPathList)340 void Manager::checkAndUpdatePowerVsVpd(
341 const nlohmann::json& i_powerVsJsonObj,
342 std::vector<std::string>& o_failedPathList)
343 {
344 for (const auto& [l_fruPath, l_recJson] : i_powerVsJsonObj.items())
345 {
346 nlohmann::json l_sysCfgJsonObj{};
347 if (m_worker.get() != nullptr)
348 {
349 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
350 }
351
352 // The utility method will handle emty JSON case. No explicit
353 // handling required here.
354 auto l_inventoryPath = jsonUtility::getInventoryObjPathFromJson(
355 l_sysCfgJsonObj, l_fruPath);
356
357 // Mark it as failed if inventory path not found in JSON.
358 if (l_inventoryPath.empty())
359 {
360 o_failedPathList.push_back(l_fruPath);
361 continue;
362 }
363
364 // check if the FRU is present
365 if (!dbusUtility::isInventoryPresent(l_inventoryPath))
366 {
367 logging::logMessage(
368 "Inventory not present, skip updating part number. Path: " +
369 l_inventoryPath);
370 continue;
371 }
372
373 // check if the FRU needs CCIN check before updating PN.
374 if (l_recJson.contains("CCIN"))
375 {
376 const auto& l_ccinFromDbus =
377 vpdSpecificUtility::getCcinFromDbus(l_inventoryPath);
378
379 // Not an ideal situation as CCIN can't be empty.
380 if (l_ccinFromDbus.empty())
381 {
382 o_failedPathList.push_back(l_fruPath);
383 continue;
384 }
385
386 std::vector<std::string> l_ccinListFromJson = l_recJson["CCIN"];
387
388 if (find(l_ccinListFromJson.begin(), l_ccinListFromJson.end(),
389 l_ccinFromDbus) == l_ccinListFromJson.end())
390 {
391 // Don't update PN in this case.
392 continue;
393 }
394 }
395
396 for (const auto& [l_recordName, l_kwdJson] : l_recJson.items())
397 {
398 // Record name can't be CCIN, skip processing as it is there for PN
399 // update based on CCIN check.
400 if (l_recordName == constants::kwdCCIN)
401 {
402 continue;
403 }
404
405 for (const auto& [l_kwdName, l_kwdValue] : l_kwdJson.items())
406 {
407 // Is value of type array.
408 if (!l_kwdValue.is_array())
409 {
410 o_failedPathList.push_back(l_fruPath);
411 continue;
412 }
413
414 // Get current FRU Part number.
415 auto l_retVal = dbusUtility::readDbusProperty(
416 constants::pimServiceName, l_inventoryPath,
417 constants::viniInf, constants::kwdFN);
418
419 auto l_ptrToFn = std::get_if<types::BinaryVector>(&l_retVal);
420
421 if (!l_ptrToFn)
422 {
423 o_failedPathList.push_back(l_fruPath);
424 continue;
425 }
426
427 types::BinaryVector l_binaryKwdValue =
428 l_kwdValue.get<types::BinaryVector>();
429 if (l_binaryKwdValue == (*l_ptrToFn))
430 {
431 continue;
432 }
433
434 // Update part number only if required.
435 if (updateKeyword(
436 l_fruPath,
437 std::make_tuple(l_recordName, l_kwdName, l_kwdValue)) ==
438 constants::FAILURE)
439 {
440 o_failedPathList.push_back(l_fruPath);
441 continue;
442 }
443
444 // update the Asset interface Spare part number explicitly.
445 if (!dbusUtility::callPIM(types::ObjectMap{
446 {l_inventoryPath,
447 {{constants::assetInf,
448 {{"SparePartNumber",
449 std::string(l_binaryKwdValue.begin(),
450 l_binaryKwdValue.end())}}}}}}))
451 {
452 logging::logMessage(
453 "Updating Spare Part Number under Asset interface failed for path [" +
454 l_inventoryPath + "]");
455 }
456
457 // Just needed for logging.
458 std::string l_initialPartNum((*l_ptrToFn).begin(),
459 (*l_ptrToFn).end());
460 std::string l_finalPartNum(l_binaryKwdValue.begin(),
461 l_binaryKwdValue.end());
462 logging::logMessage(
463 "FRU Part number updated for path [" + l_inventoryPath +
464 "]" + "From [" + l_initialPartNum + "]" + " to [" +
465 l_finalPartNum + "]");
466 }
467 }
468 }
469 }
470
ConfigurePowerVsSystem()471 void Manager::ConfigurePowerVsSystem()
472 {
473 std::vector<std::string> l_failedPathList;
474 try
475 {
476 types::BinaryVector l_imValue = dbusUtility::getImFromDbus();
477 if (l_imValue.empty())
478 {
479 throw DbusException("Invalid IM value read from Dbus");
480 }
481
482 if (!vpdSpecificUtility::isPowerVsConfiguration(l_imValue))
483 {
484 // TODO: Should booting be blocked in case of some
485 // misconfigurations?
486 return;
487 }
488
489 const nlohmann::json& l_powerVsJsonObj =
490 jsonUtility::getPowerVsJson(l_imValue);
491
492 if (l_powerVsJsonObj.empty())
493 {
494 throw std::runtime_error("PowerVS Json not found");
495 }
496
497 checkAndUpdatePowerVsVpd(l_powerVsJsonObj, l_failedPathList);
498
499 if (!l_failedPathList.empty())
500 {
501 throw std::runtime_error(
502 "Part number update failed for following paths: ");
503 }
504 }
505 catch (const std::exception& l_ex)
506 {
507 // TODO log appropriate PEL
508 }
509 }
510
processFailedEeproms()511 void Manager::processFailedEeproms()
512 {
513 if (m_worker.get() != nullptr)
514 {
515 // TODO:
516 // - iterate through list of EEPROMs for which thread creation has
517 // failed
518 // - For each failed EEPROM, trigger VPD collection
519 m_worker->getFailedEepromPaths().clear();
520 }
521 }
522 #endif
523
updateKeyword(const types::Path i_vpdPath,const types::WriteVpdParams i_paramsToWriteData)524 int Manager::updateKeyword(const types::Path i_vpdPath,
525 const types::WriteVpdParams i_paramsToWriteData)
526 {
527 if (i_vpdPath.empty())
528 {
529 logging::logMessage("Given VPD path is empty.");
530 return -1;
531 }
532
533 types::Path l_fruPath;
534 nlohmann::json l_sysCfgJsonObj{};
535
536 if (m_worker.get() != nullptr)
537 {
538 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
539
540 // Get the EEPROM path
541 if (!l_sysCfgJsonObj.empty())
542 {
543 l_fruPath =
544 jsonUtility::getFruPathFromJson(l_sysCfgJsonObj, i_vpdPath);
545 }
546 }
547
548 if (l_fruPath.empty())
549 {
550 l_fruPath = i_vpdPath;
551 }
552
553 try
554 {
555 std::shared_ptr<Parser> l_parserObj =
556 std::make_shared<Parser>(l_fruPath, l_sysCfgJsonObj);
557 return l_parserObj->updateVpdKeyword(i_paramsToWriteData);
558 }
559 catch (const std::exception& l_exception)
560 {
561 // TODO:: error log needed
562 logging::logMessage("Update keyword failed for file[" + i_vpdPath +
563 "], reason: " + std::string(l_exception.what()));
564 return -1;
565 }
566 }
567
updateKeywordOnHardware(const types::Path i_fruPath,const types::WriteVpdParams i_paramsToWriteData)568 int Manager::updateKeywordOnHardware(
569 const types::Path i_fruPath,
570 const types::WriteVpdParams i_paramsToWriteData) noexcept
571 {
572 try
573 {
574 if (i_fruPath.empty())
575 {
576 throw std::runtime_error("Given FRU path is empty");
577 }
578
579 nlohmann::json l_sysCfgJsonObj{};
580
581 if (m_worker.get() != nullptr)
582 {
583 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
584 }
585
586 std::shared_ptr<Parser> l_parserObj =
587 std::make_shared<Parser>(i_fruPath, l_sysCfgJsonObj);
588 return l_parserObj->updateVpdKeywordOnHardware(i_paramsToWriteData);
589 }
590 catch (const std::exception& l_exception)
591 {
592 EventLogger::createAsyncPel(
593 types::ErrorType::InvalidEeprom, types::SeverityType::Informational,
594 __FILE__, __FUNCTION__, 0,
595 "Update keyword on hardware failed for file[" + i_fruPath +
596 "], reason: " + std::string(l_exception.what()),
597 std::nullopt, std::nullopt, std::nullopt, std::nullopt);
598
599 return constants::FAILURE;
600 }
601 }
602
readKeyword(const types::Path i_fruPath,const types::ReadVpdParams i_paramsToReadData)603 types::DbusVariantType Manager::readKeyword(
604 const types::Path i_fruPath, const types::ReadVpdParams i_paramsToReadData)
605 {
606 try
607 {
608 nlohmann::json l_jsonObj{};
609
610 if (m_worker.get() != nullptr)
611 {
612 l_jsonObj = m_worker->getSysCfgJsonObj();
613 }
614
615 std::error_code ec;
616
617 // Check if given path is filesystem path
618 if (!std::filesystem::exists(i_fruPath, ec) && (ec))
619 {
620 throw std::runtime_error(
621 "Given file path " + i_fruPath + " not found.");
622 }
623
624 logging::logMessage("Performing VPD read on " + i_fruPath);
625
626 std::shared_ptr<vpd::Parser> l_parserObj =
627 std::make_shared<vpd::Parser>(i_fruPath, l_jsonObj);
628
629 std::shared_ptr<vpd::ParserInterface> l_vpdParserInstance =
630 l_parserObj->getVpdParserInstance();
631
632 return (
633 l_vpdParserInstance->readKeywordFromHardware(i_paramsToReadData));
634 }
635 catch (const std::exception& e)
636 {
637 logging::logMessage(
638 e.what() + std::string(". VPD manager read operation failed for ") +
639 i_fruPath);
640 throw types::DeviceError::ReadFailure();
641 }
642 }
643
collectSingleFruVpd(const sdbusplus::message::object_path & i_dbusObjPath)644 void Manager::collectSingleFruVpd(
645 const sdbusplus::message::object_path& i_dbusObjPath)
646 {
647 try
648 {
649 if (m_vpdCollectionStatus != "Completed")
650 {
651 logging::logMessage(
652 "Currently VPD CollectionStatus is not completed. Cannot perform single FRU VPD collection for " +
653 std::string(i_dbusObjPath));
654 return;
655 }
656
657 // Get system config JSON object from worker class
658 nlohmann::json l_sysCfgJsonObj{};
659
660 if (m_worker.get() != nullptr)
661 {
662 l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
663 }
664
665 // Check if system config JSON is present
666 if (l_sysCfgJsonObj.empty())
667 {
668 logging::logMessage(
669 "System config JSON object not present. Single FRU VPD collection is not performed for " +
670 std::string(i_dbusObjPath));
671 return;
672 }
673
674 // Get FRU path for the given D-bus object path from JSON
675 const std::string& l_fruPath =
676 jsonUtility::getFruPathFromJson(l_sysCfgJsonObj, i_dbusObjPath);
677
678 if (l_fruPath.empty())
679 {
680 logging::logMessage(
681 "D-bus object path not present in JSON. Single FRU VPD collection is not performed for " +
682 std::string(i_dbusObjPath));
683 return;
684 }
685
686 // Check if host is up and running
687 if (dbusUtility::isHostRunning())
688 {
689 if (!jsonUtility::isFruReplaceableAtRuntime(l_sysCfgJsonObj,
690 l_fruPath))
691 {
692 logging::logMessage(
693 "Given FRU is not replaceable at host runtime. Single FRU VPD collection is not performed for " +
694 std::string(i_dbusObjPath));
695 return;
696 }
697 }
698 else if (dbusUtility::isBMCReady())
699 {
700 if (!jsonUtility::isFruReplaceableAtStandby(l_sysCfgJsonObj,
701 l_fruPath) &&
702 (!jsonUtility::isFruReplaceableAtRuntime(l_sysCfgJsonObj,
703 l_fruPath)))
704 {
705 logging::logMessage(
706 "Given FRU is neither replaceable at standby nor replaceable at runtime. Single FRU VPD collection is not performed for " +
707 std::string(i_dbusObjPath));
708 return;
709 }
710 }
711
712 // Set CollectionStatus as InProgress. Since it's an intermediate state
713 // D-bus set-property call is good enough to update the status.
714 const std::string& l_collStatusProp = "CollectionStatus";
715
716 if (!dbusUtility::writeDbusProperty(
717 jsonUtility::getServiceName(l_sysCfgJsonObj,
718 std::string(i_dbusObjPath)),
719 std::string(i_dbusObjPath), constants::vpdCollectionInterface,
720 l_collStatusProp,
721 types::DbusVariantType{constants::vpdCollectionInProgress}))
722 {
723 logging::logMessage(
724 "Unable to set CollectionStatus as InProgress for " +
725 std::string(i_dbusObjPath) +
726 ". Continue single FRU VPD collection.");
727 }
728
729 // Parse VPD
730 types::VPDMapVariant l_parsedVpd = m_worker->parseVpdFile(l_fruPath);
731
732 // If l_parsedVpd is pointing to std::monostate
733 if (l_parsedVpd.index() == 0)
734 {
735 throw std::runtime_error(
736 "VPD parsing failed for " + std::string(i_dbusObjPath));
737 }
738
739 // Get D-bus object map from worker class
740 types::ObjectMap l_dbusObjectMap;
741 m_worker->populateDbus(l_parsedVpd, l_dbusObjectMap, l_fruPath);
742
743 if (l_dbusObjectMap.empty())
744 {
745 throw std::runtime_error(
746 "Failed to create D-bus object map. Single FRU VPD collection failed for " +
747 std::string(i_dbusObjPath));
748 }
749
750 // Call PIM's Notify method
751 if (!dbusUtility::callPIM(move(l_dbusObjectMap)))
752 {
753 throw std::runtime_error(
754 "Notify PIM failed. Single FRU VPD collection failed for " +
755 std::string(i_dbusObjPath));
756 }
757 }
758 catch (const std::exception& l_error)
759 {
760 // Notify FRU's VPD CollectionStatus as Failure
761 if (!dbusUtility::notifyFRUCollectionStatus(
762 std::string(i_dbusObjPath), constants::vpdCollectionFailure))
763 {
764 logging::logMessage(
765 "Call to PIM Notify method failed to update Collection status as Failure for " +
766 std::string(i_dbusObjPath));
767 }
768
769 // TODO: Log PEL
770 logging::logMessage(std::string(l_error.what()));
771 }
772 }
773
deleteSingleFruVpd(const sdbusplus::message::object_path & i_dbusObjPath)774 void Manager::deleteSingleFruVpd(
775 const sdbusplus::message::object_path& i_dbusObjPath)
776 {
777 try
778 {
779 if (std::string(i_dbusObjPath).empty())
780 {
781 throw std::runtime_error(
782 "Given DBus object path is empty. Aborting FRU VPD deletion.");
783 }
784
785 if (m_worker.get() == nullptr)
786 {
787 throw std::runtime_error(
788 "Worker object not found, can't perform FRU VPD deletion for: " +
789 std::string(i_dbusObjPath));
790 }
791
792 m_worker->deleteFruVpd(std::string(i_dbusObjPath));
793 }
794 catch (const std::exception& l_ex)
795 {
796 // TODO: Log PEL
797 logging::logMessage(l_ex.what());
798 }
799 }
800
isValidUnexpandedLocationCode(const std::string & i_unexpandedLocationCode)801 bool Manager::isValidUnexpandedLocationCode(
802 const std::string& i_unexpandedLocationCode)
803 {
804 if ((i_unexpandedLocationCode.length() <
805 constants::UNEXP_LOCATION_CODE_MIN_LENGTH) ||
806 ((i_unexpandedLocationCode.compare(0, 4, "Ufcs") !=
807 constants::STR_CMP_SUCCESS) &&
808 (i_unexpandedLocationCode.compare(0, 4, "Umts") !=
809 constants::STR_CMP_SUCCESS)) ||
810 ((i_unexpandedLocationCode.length() >
811 constants::UNEXP_LOCATION_CODE_MIN_LENGTH) &&
812 (i_unexpandedLocationCode.find("-") != 4)))
813 {
814 return false;
815 }
816
817 return true;
818 }
819
getExpandedLocationCode(const std::string & i_unexpandedLocationCode,const uint16_t i_nodeNumber)820 std::string Manager::getExpandedLocationCode(
821 const std::string& i_unexpandedLocationCode,
822 [[maybe_unused]] const uint16_t i_nodeNumber)
823 {
824 if (!isValidUnexpandedLocationCode(i_unexpandedLocationCode))
825 {
826 phosphor::logging::elog<types::DbusInvalidArgument>(
827 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
828 types::InvalidArgument::ARGUMENT_VALUE(
829 i_unexpandedLocationCode.c_str()));
830 }
831
832 const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
833 if (!l_sysCfgJsonObj.contains("frus"))
834 {
835 logging::logMessage("Missing frus tag in system config JSON");
836 }
837
838 const nlohmann::json& l_listOfFrus =
839 l_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>();
840
841 for (const auto& l_frus : l_listOfFrus.items())
842 {
843 for (const auto& l_aFru : l_frus.value())
844 {
845 if (l_aFru["extraInterfaces"].contains(
846 constants::locationCodeInf) &&
847 l_aFru["extraInterfaces"][constants::locationCodeInf].value(
848 "LocationCode", "") == i_unexpandedLocationCode)
849 {
850 return std::get<std::string>(dbusUtility::readDbusProperty(
851 l_aFru["serviceName"], l_aFru["inventoryPath"],
852 constants::locationCodeInf, "LocationCode"));
853 }
854 }
855 }
856 phosphor::logging::elog<types::DbusInvalidArgument>(
857 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
858 types::InvalidArgument::ARGUMENT_VALUE(
859 i_unexpandedLocationCode.c_str()));
860 }
861
getFrusByUnexpandedLocationCode(const std::string & i_unexpandedLocationCode,const uint16_t i_nodeNumber)862 types::ListOfPaths Manager::getFrusByUnexpandedLocationCode(
863 const std::string& i_unexpandedLocationCode,
864 [[maybe_unused]] const uint16_t i_nodeNumber)
865 {
866 types::ListOfPaths l_inventoryPaths;
867
868 if (!isValidUnexpandedLocationCode(i_unexpandedLocationCode))
869 {
870 phosphor::logging::elog<types::DbusInvalidArgument>(
871 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
872 types::InvalidArgument::ARGUMENT_VALUE(
873 i_unexpandedLocationCode.c_str()));
874 }
875
876 const nlohmann::json& l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
877 if (!l_sysCfgJsonObj.contains("frus"))
878 {
879 logging::logMessage("Missing frus tag in system config JSON");
880 }
881
882 const nlohmann::json& l_listOfFrus =
883 l_sysCfgJsonObj["frus"].get_ref<const nlohmann::json::object_t&>();
884
885 for (const auto& l_frus : l_listOfFrus.items())
886 {
887 for (const auto& l_aFru : l_frus.value())
888 {
889 if (l_aFru["extraInterfaces"].contains(
890 constants::locationCodeInf) &&
891 l_aFru["extraInterfaces"][constants::locationCodeInf].value(
892 "LocationCode", "") == i_unexpandedLocationCode)
893 {
894 l_inventoryPaths.push_back(
895 l_aFru.at("inventoryPath")
896 .get_ref<const nlohmann::json::string_t&>());
897 }
898 }
899 }
900
901 if (l_inventoryPaths.empty())
902 {
903 phosphor::logging::elog<types::DbusInvalidArgument>(
904 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
905 types::InvalidArgument::ARGUMENT_VALUE(
906 i_unexpandedLocationCode.c_str()));
907 }
908
909 return l_inventoryPaths;
910 }
911
getHwPath(const sdbusplus::message::object_path & i_dbusObjPath)912 std::string Manager::getHwPath(
913 const sdbusplus::message::object_path& i_dbusObjPath)
914 {
915 // Dummy code to supress unused variable warning. To be removed.
916 logging::logMessage(std::string(i_dbusObjPath));
917
918 return std::string{};
919 }
920
getUnexpandedLocationCode(const std::string & i_expandedLocationCode)921 std::tuple<std::string, uint16_t> Manager::getUnexpandedLocationCode(
922 const std::string& i_expandedLocationCode)
923 {
924 /**
925 * Location code should always start with U and fulfil minimum length
926 * criteria.
927 */
928 if (i_expandedLocationCode[0] != 'U' ||
929 i_expandedLocationCode.length() <
930 constants::EXP_LOCATION_CODE_MIN_LENGTH)
931 {
932 phosphor::logging::elog<types::DbusInvalidArgument>(
933 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
934 types::InvalidArgument::ARGUMENT_VALUE(
935 i_expandedLocationCode.c_str()));
936 }
937
938 std::string l_fcKwd;
939
940 auto l_fcKwdValue = dbusUtility::readDbusProperty(
941 "xyz.openbmc_project.Inventory.Manager",
942 "/xyz/openbmc_project/inventory/system/chassis/motherboard",
943 "com.ibm.ipzvpd.VCEN", "FC");
944
945 if (auto l_kwdValue = std::get_if<types::BinaryVector>(&l_fcKwdValue))
946 {
947 l_fcKwd.assign(l_kwdValue->begin(), l_kwdValue->end());
948 }
949
950 // Get the first part of expanded location code to check for FC or TM.
951 std::string l_firstKwd = i_expandedLocationCode.substr(1, 4);
952
953 std::string l_unexpandedLocationCode{};
954 uint16_t l_nodeNummber = constants::INVALID_NODE_NUMBER;
955
956 // Check if this value matches the value of FC keyword.
957 if (l_fcKwd.substr(0, 4) == l_firstKwd)
958 {
959 /**
960 * Period(.) should be there in expanded location code to seggregate
961 * FC, node number and SE values.
962 */
963 size_t l_nodeStartPos = i_expandedLocationCode.find('.');
964 if (l_nodeStartPos == std::string::npos)
965 {
966 phosphor::logging::elog<types::DbusInvalidArgument>(
967 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
968 types::InvalidArgument::ARGUMENT_VALUE(
969 i_expandedLocationCode.c_str()));
970 }
971
972 size_t l_nodeEndPos =
973 i_expandedLocationCode.find('.', l_nodeStartPos + 1);
974 if (l_nodeEndPos == std::string::npos)
975 {
976 phosphor::logging::elog<types::DbusInvalidArgument>(
977 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
978 types::InvalidArgument::ARGUMENT_VALUE(
979 i_expandedLocationCode.c_str()));
980 }
981
982 // Skip 3 bytes for '.ND'
983 l_nodeNummber = std::stoi(i_expandedLocationCode.substr(
984 l_nodeStartPos + 3, (l_nodeEndPos - l_nodeStartPos - 3)));
985
986 /**
987 * Confirm if there are other details apart FC, node number and SE
988 * in location code
989 */
990 if (i_expandedLocationCode.length() >
991 constants::EXP_LOCATION_CODE_MIN_LENGTH)
992 {
993 l_unexpandedLocationCode =
994 i_expandedLocationCode[0] + std::string("fcs") +
995 i_expandedLocationCode.substr(
996 l_nodeEndPos + 1 + constants::SE_KWD_LENGTH,
997 std::string::npos);
998 }
999 else
1000 {
1001 l_unexpandedLocationCode = "Ufcs";
1002 }
1003 }
1004 else
1005 {
1006 std::string l_tmKwd;
1007 // Read TM keyword value.
1008 auto l_tmKwdValue = dbusUtility::readDbusProperty(
1009 "xyz.openbmc_project.Inventory.Manager",
1010 "/xyz/openbmc_project/inventory/system/chassis/motherboard",
1011 "com.ibm.ipzvpd.VSYS", "TM");
1012
1013 if (auto l_kwdValue = std::get_if<types::BinaryVector>(&l_tmKwdValue))
1014 {
1015 l_tmKwd.assign(l_kwdValue->begin(), l_kwdValue->end());
1016 }
1017
1018 // Check if the substr matches to TM keyword value.
1019 if (l_tmKwd.substr(0, 4) == l_firstKwd)
1020 {
1021 /**
1022 * System location code will not have node number and any other
1023 * details.
1024 */
1025 l_unexpandedLocationCode = "Umts";
1026 }
1027 // The given location code is neither "fcs" or "mts".
1028 else
1029 {
1030 phosphor::logging::elog<types::DbusInvalidArgument>(
1031 types::InvalidArgument::ARGUMENT_NAME("LOCATIONCODE"),
1032 types::InvalidArgument::ARGUMENT_VALUE(
1033 i_expandedLocationCode.c_str()));
1034 }
1035 }
1036
1037 return std::make_tuple(l_unexpandedLocationCode, l_nodeNummber);
1038 }
1039
getFrusByExpandedLocationCode(const std::string & i_expandedLocationCode)1040 types::ListOfPaths Manager::getFrusByExpandedLocationCode(
1041 const std::string& i_expandedLocationCode)
1042 {
1043 std::tuple<std::string, uint16_t> l_locationAndNodePair =
1044 getUnexpandedLocationCode(i_expandedLocationCode);
1045
1046 return getFrusByUnexpandedLocationCode(std::get<0>(l_locationAndNodePair),
1047 std::get<1>(l_locationAndNodePair));
1048 }
1049
registerHostStateChangeCallback()1050 void Manager::registerHostStateChangeCallback()
1051 {
1052 static std::shared_ptr<sdbusplus::bus::match_t> l_hostState =
1053 std::make_shared<sdbusplus::bus::match_t>(
1054 *m_asioConnection,
1055 sdbusplus::bus::match::rules::propertiesChanged(
1056 constants::hostObjectPath, constants::hostInterface),
1057 [this](sdbusplus::message_t& i_msg) {
1058 hostStateChangeCallBack(i_msg);
1059 });
1060 }
1061
hostStateChangeCallBack(sdbusplus::message_t & i_msg)1062 void Manager::hostStateChangeCallBack(sdbusplus::message_t& i_msg)
1063 {
1064 try
1065 {
1066 if (i_msg.is_method_error())
1067 {
1068 throw std::runtime_error(
1069 "Error reading callback message for host state");
1070 }
1071
1072 std::string l_objectPath;
1073 types::PropertyMap l_propMap;
1074 i_msg.read(l_objectPath, l_propMap);
1075
1076 const auto l_itr = l_propMap.find("CurrentHostState");
1077
1078 if (l_itr == l_propMap.end())
1079 {
1080 throw std::runtime_error(
1081 "CurrentHostState field is missing in callback message");
1082 }
1083
1084 if (auto l_hostState = std::get_if<std::string>(&(l_itr->second)))
1085 {
1086 // implies system is moving from standby to power on state
1087 if (*l_hostState == "xyz.openbmc_project.State.Host.HostState."
1088 "TransitioningToRunning")
1089 {
1090 // TODO: check for all the essential FRUs in the system.
1091
1092 // Perform recollection.
1093 performVpdRecollection();
1094 return;
1095 }
1096 }
1097 else
1098 {
1099 throw std::runtime_error(
1100 "Invalid type recieved in variant for host state.");
1101 }
1102 }
1103 catch (const std::exception& l_ex)
1104 {
1105 // TODO: Log PEL.
1106 logging::logMessage(l_ex.what());
1107 }
1108 }
1109
performVpdRecollection()1110 void Manager::performVpdRecollection()
1111 {
1112 try
1113 {
1114 if (m_worker.get() != nullptr)
1115 {
1116 nlohmann::json l_sysCfgJsonObj = m_worker->getSysCfgJsonObj();
1117
1118 // Check if system config JSON is present
1119 if (l_sysCfgJsonObj.empty())
1120 {
1121 throw std::runtime_error(
1122 "System config json object is empty, can't process recollection.");
1123 }
1124
1125 const auto& l_frusReplaceableAtStandby =
1126 jsonUtility::getListOfFrusReplaceableAtStandby(l_sysCfgJsonObj);
1127
1128 for (const auto& l_fruInventoryPath : l_frusReplaceableAtStandby)
1129 {
1130 // ToDo: Add some logic/trace to know the flow to
1131 // collectSingleFruVpd has been directed via
1132 // performVpdRecollection.
1133 collectSingleFruVpd(l_fruInventoryPath);
1134 }
1135 return;
1136 }
1137
1138 throw std::runtime_error(
1139 "Worker object not found can't process recollection");
1140 }
1141 catch (const std::exception& l_ex)
1142 {
1143 // TODO Log PEL
1144 logging::logMessage(
1145 "VPD recollection failed with error: " + std::string(l_ex.what()));
1146 }
1147 }
1148 } // namespace vpd
1149