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