1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "data_interface.hpp"
18 
19 #include "util.hpp"
20 
21 #include <phosphor-logging/lg2.hpp>
22 #include <xyz/openbmc_project/State/BMC/server.hpp>
23 #include <xyz/openbmc_project/State/Boot/Progress/server.hpp>
24 
25 #include <filesystem>
26 
27 #ifdef PEL_ENABLE_PHAL
28 #include <libekb.H>
29 #include <libpdbg.h>
30 #include <libphal.H>
31 #endif
32 
33 // Use a timeout of 10s for D-Bus calls so if there are
34 // timeouts the callers of the PEL creation method won't
35 // also timeout.
36 constexpr auto dbusTimeout = 10000000;
37 
38 namespace openpower
39 {
40 namespace pels
41 {
42 
43 namespace service_name
44 {
45 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
46 constexpr auto vpdManager = "com.ibm.VPD.Manager";
47 constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager";
48 constexpr auto hwIsolation = "org.open_power.HardwareIsolation";
49 constexpr auto biosConfigMgr = "xyz.openbmc_project.BIOSConfigManager";
50 constexpr auto bootRawProgress = "xyz.openbmc_project.State.Boot.Raw";
51 constexpr auto pldm = "xyz.openbmc_project.PLDM";
52 constexpr auto inventoryManager = "xyz.openbmc_project.Inventory.Manager";
53 constexpr auto entityManager = "xyz.openbmc_project.EntityManager";
54 constexpr auto systemd = "org.freedesktop.systemd1";
55 } // namespace service_name
56 
57 namespace object_path
58 {
59 constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper";
60 constexpr auto systemInv = "/xyz/openbmc_project/inventory/system";
61 constexpr auto motherBoardInv =
62     "/xyz/openbmc_project/inventory/system/chassis/motherboard";
63 constexpr auto baseInv = "/xyz/openbmc_project/inventory";
64 constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0";
65 constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0";
66 constexpr auto hostState = "/xyz/openbmc_project/state/host0";
67 constexpr auto enableHostPELs =
68     "/xyz/openbmc_project/logging/send_event_logs_to_host";
69 constexpr auto vpdManager = "/com/ibm/VPD/Manager";
70 constexpr auto logSetting = "/xyz/openbmc_project/logging/settings";
71 constexpr auto hwIsolation = "/xyz/openbmc_project/hardware_isolation";
72 constexpr auto biosConfigMgr = "/xyz/openbmc_project/bios_config/manager";
73 constexpr auto bootRawProgress = "/xyz/openbmc_project/state/boot/raw0";
74 constexpr auto systemd = "/org/freedesktop/systemd1";
75 } // namespace object_path
76 
77 namespace interface
78 {
79 constexpr auto dbusProperty = "org.freedesktop.DBus.Properties";
80 constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper";
81 constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset";
82 constexpr auto bootProgress = "xyz.openbmc_project.State.Boot.Progress";
83 constexpr auto enable = "xyz.openbmc_project.Object.Enable";
84 constexpr auto bmcState = "xyz.openbmc_project.State.BMC";
85 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis";
86 constexpr auto hostState = "xyz.openbmc_project.State.Host";
87 constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI";
88 constexpr auto vsbpRecordVPD = "com.ibm.ipzvpd.VSBP";
89 constexpr auto locCode = "xyz.openbmc_project.Inventory.Decorator.LocationCode";
90 constexpr auto compatible =
91     "xyz.openbmc_project.Inventory.Decorator.Compatible";
92 constexpr auto vpdManager = "com.ibm.VPD.Manager";
93 constexpr auto ledGroup = "xyz.openbmc_project.Led.Group";
94 constexpr auto operationalStatus =
95     "xyz.openbmc_project.State.Decorator.OperationalStatus";
96 constexpr auto logSetting = "xyz.openbmc_project.Logging.Settings";
97 constexpr auto associationDef = "xyz.openbmc_project.Association.Definitions";
98 constexpr auto hwIsolationEntry = "xyz.openbmc_project.HardwareIsolation.Entry";
99 constexpr auto association = "xyz.openbmc_project.Association";
100 constexpr auto biosConfigMgr = "xyz.openbmc_project.BIOSConfig.Manager";
101 constexpr auto bootRawProgress = "xyz.openbmc_project.State.Boot.Raw";
102 constexpr auto invItem = "xyz.openbmc_project.Inventory.Item";
103 constexpr auto invFan = "xyz.openbmc_project.Inventory.Item.Fan";
104 constexpr auto invPowerSupply =
105     "xyz.openbmc_project.Inventory.Item.PowerSupply";
106 constexpr auto inventoryManager = "xyz.openbmc_project.Inventory.Manager";
107 constexpr auto systemdMgr = "org.freedesktop.systemd1.Manager";
108 } // namespace interface
109 
110 using namespace sdbusplus::server::xyz::openbmc_project::state::boot;
111 using namespace sdbusplus::server::xyz::openbmc_project::state;
112 namespace match_rules = sdbusplus::bus::match::rules;
113 
114 const DBusInterfaceList hotplugInterfaces{interface::invFan,
115                                           interface::invPowerSupply};
116 static constexpr auto PDBG_DTB_PATH =
117     "/var/lib/phosphor-software-manager/hostfw/running/DEVTREE";
118 
119 std::pair<std::string, std::string>
120     DataInterfaceBase::extractConnectorFromLocCode(
121         const std::string& locationCode)
122 {
123     auto base = locationCode;
124     std::string connector{};
125 
126     auto pos = base.find("-T");
127     if (pos != std::string::npos)
128     {
129         connector = base.substr(pos);
130         base = base.substr(0, pos);
131     }
132 
133     return {base, connector};
134 }
135 
136 DataInterface::DataInterface(sdbusplus::bus_t& bus) :
137     _bus(bus), _systemdSlot(nullptr)
138 {
139     readBMCFWVersion();
140     readServerFWVersion();
141     readBMCFWVersionID();
142 
143     // Watch the BootProgress property
144     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
145         bus, object_path::hostState, interface::bootProgress, "BootProgress",
146         *this, [this](const auto& value) {
147             this->_bootState = std::get<std::string>(value);
148             auto status = Progress::convertProgressStagesFromString(
149                 std::get<std::string>(value));
150 
151             if ((status == Progress::ProgressStages::SystemInitComplete) ||
152                 (status == Progress::ProgressStages::OSRunning))
153             {
154                 setHostUp(true);
155             }
156             else
157             {
158                 setHostUp(false);
159             }
160         }));
161 
162     // Watch the host PEL enable property
163     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
164         bus, object_path::enableHostPELs, interface::enable, "Enabled", *this,
165         [this](const auto& value) {
166             if (std::get<bool>(value) != this->_sendPELsToHost)
167             {
168                 lg2::info("The send PELs to host setting changed to {VAL}",
169                           "VAL", std::get<bool>(value));
170             }
171             this->_sendPELsToHost = std::get<bool>(value);
172         }));
173 
174     // Watch the BMCState property
175     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
176         bus, object_path::bmcState, interface::bmcState, "CurrentBMCState",
177         *this, [this](const auto& value) {
178             const auto& state = std::get<std::string>(value);
179             this->_bmcState = state;
180 
181             // Wait for BMC ready to start watching for
182             // plugs so things calm down first.
183             if (BMC::convertBMCStateFromString(state) == BMC::BMCState::Ready)
184             {
185                 startFruPlugWatch();
186             }
187         }));
188 
189     // Watch the chassis current and requested power state properties
190     _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>(
191         bus, object_path::chassisState, interface::chassisState, *this,
192         [this](const auto& properties) {
193             auto state = properties.find("CurrentPowerState");
194             if (state != properties.end())
195             {
196                 this->_chassisState = std::get<std::string>(state->second);
197             }
198 
199             auto trans = properties.find("RequestedPowerTransition");
200             if (trans != properties.end())
201             {
202                 this->_chassisTransition = std::get<std::string>(trans->second);
203             }
204         }));
205 
206     // Watch the CurrentHostState property
207     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
208         bus, object_path::hostState, interface::hostState, "CurrentHostState",
209         *this, [this](const auto& value) {
210             this->_hostState = std::get<std::string>(value);
211         }));
212 
213     // Watch the BaseBIOSTable property for the hmc managed attribute
214     _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
215         bus, object_path::biosConfigMgr, interface::biosConfigMgr,
216         "BaseBIOSTable", service_name::biosConfigMgr, *this,
217         [this](const auto& value) {
218             const auto& attributes = std::get<BiosAttributes>(value);
219 
220             auto it = attributes.find("pvm_hmc_managed");
221             if (it != attributes.end())
222             {
223                 const auto& currentValVariant = std::get<5>(it->second);
224                 auto currentVal = std::get_if<std::string>(&currentValVariant);
225                 if (currentVal)
226                 {
227                     this->_hmcManaged =
228                         (*currentVal == "Enabled") ? true : false;
229                 }
230             }
231         }));
232 
233     if (isPHALDevTreeExist())
234     {
235 #ifdef PEL_ENABLE_PHAL
236         initPHAL();
237         try
238         {
239             libguard::libguard_init(false);
240         }
241         catch (libguard::exception::GuardException& e)
242         {
243             lg2::error("Exception to init the guard {ERROR}", "ERROR",
244                        e.what());
245         }
246 #endif
247     }
248     else
249     {
250         // Watch the "openpower-update-bios-attr-table" service to init
251         // PHAL libraries
252         subscribeToSystemdSignals();
253     }
254 }
255 
256 DBusPropertyMap DataInterface::getAllProperties(
257     const std::string& service, const std::string& objectPath,
258     const std::string& interface) const
259 {
260     DBusPropertyMap properties;
261 
262     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
263                                        interface::dbusProperty, "GetAll");
264     method.append(interface);
265     auto reply = _bus.call(method, dbusTimeout);
266 
267     reply.read(properties);
268 
269     return properties;
270 }
271 
272 void DataInterface::getProperty(
273     const std::string& service, const std::string& objectPath,
274     const std::string& interface, const std::string& property,
275     DBusValue& value) const
276 {
277     auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
278                                        interface::dbusProperty, "Get");
279     method.append(interface, property);
280     auto reply = _bus.call(method, dbusTimeout);
281 
282     reply.read(value);
283 }
284 
285 DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const
286 {
287     auto method = _bus.new_method_call(
288         service_name::objectMapper, object_path::objectMapper,
289         interface::objectMapper, "GetSubTreePaths");
290 
291     method.append(std::string{"/"}, 0, interfaces);
292 
293     auto reply = _bus.call(method, dbusTimeout);
294 
295     DBusPathList paths;
296     reply.read(paths);
297 
298     return paths;
299 }
300 
301 DBusService DataInterface::getService(const std::string& objectPath,
302                                       const std::string& interface) const
303 {
304     auto method = _bus.new_method_call(service_name::objectMapper,
305                                        object_path::objectMapper,
306                                        interface::objectMapper, "GetObject");
307 
308     method.append(objectPath, std::vector<std::string>({interface}));
309 
310     auto reply = _bus.call(method, dbusTimeout);
311 
312     std::map<DBusService, DBusInterfaceList> response;
313     reply.read(response);
314 
315     if (!response.empty())
316     {
317         return response.begin()->first;
318     }
319 
320     return std::string{};
321 }
322 
323 void DataInterface::readBMCFWVersion()
324 {
325     _bmcFWVersion =
326         phosphor::logging::util::getOSReleaseValue("VERSION").value_or("");
327 }
328 
329 void DataInterface::readServerFWVersion()
330 {
331     auto value =
332         phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
333     if ((value != "") && (value.find_last_of(')') != std::string::npos))
334     {
335         std::size_t pos = value.find_first_of('(') + 1;
336         _serverFWVersion = value.substr(pos, value.find_last_of(')') - pos);
337     }
338 }
339 
340 void DataInterface::readBMCFWVersionID()
341 {
342     _bmcFWVersionID =
343         phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or("");
344 }
345 
346 std::string DataInterface::getMachineTypeModel() const
347 {
348     std::string model;
349     try
350     {
351         auto service = getService(object_path::systemInv, interface::invAsset);
352         if (!service.empty())
353         {
354             DBusValue value;
355             getProperty(service, object_path::systemInv, interface::invAsset,
356                         "Model", value);
357 
358             model = std::get<std::string>(value);
359         }
360     }
361     catch (const std::exception& e)
362     {
363         lg2::warning("Failed reading Model property from "
364                      "interface: {IFACE} exception: {ERROR}",
365                      "IFACE", interface::invAsset, "ERROR", e);
366     }
367 
368     return model;
369 }
370 
371 std::string DataInterface::getMachineSerialNumber() const
372 {
373     std::string sn;
374     try
375     {
376         auto service = getService(object_path::systemInv, interface::invAsset);
377         if (!service.empty())
378         {
379             DBusValue value;
380             getProperty(service, object_path::systemInv, interface::invAsset,
381                         "SerialNumber", value);
382 
383             sn = std::get<std::string>(value);
384         }
385     }
386     catch (const std::exception& e)
387     {
388         lg2::warning("Failed reading SerialNumber property from "
389                      "interface: {IFACE} exception: {ERROR}",
390                      "IFACE", interface::invAsset, "ERROR", e);
391     }
392 
393     return sn;
394 }
395 
396 std::string DataInterface::getMotherboardCCIN() const
397 {
398     std::string ccin;
399 
400     try
401     {
402         auto service =
403             getService(object_path::motherBoardInv, interface::viniRecordVPD);
404         if (!service.empty())
405         {
406             DBusValue value;
407             getProperty(service, object_path::motherBoardInv,
408                         interface::viniRecordVPD, "CC", value);
409 
410             auto cc = std::get<std::vector<uint8_t>>(value);
411             ccin = std::string{cc.begin(), cc.end()};
412         }
413     }
414     catch (const std::exception& e)
415     {
416         lg2::warning("Failed reading Motherboard CCIN property from "
417                      "interface: {IFACE} exception: {ERROR}",
418                      "IFACE", interface::viniRecordVPD, "ERROR", e);
419     }
420 
421     return ccin;
422 }
423 
424 std::vector<uint8_t> DataInterface::getSystemIMKeyword() const
425 {
426     std::vector<uint8_t> systemIM;
427 
428     try
429     {
430         auto service =
431             getService(object_path::motherBoardInv, interface::vsbpRecordVPD);
432         if (!service.empty())
433         {
434             DBusValue value;
435             getProperty(service, object_path::motherBoardInv,
436                         interface::vsbpRecordVPD, "IM", value);
437 
438             systemIM = std::get<std::vector<uint8_t>>(value);
439         }
440     }
441     catch (const std::exception& e)
442     {
443         lg2::warning("Failed reading System IM property from "
444                      "interface: {IFACE} exception: {ERROR}",
445                      "IFACE", interface::vsbpRecordVPD, "ERROR", e);
446     }
447 
448     return systemIM;
449 }
450 
451 void DataInterface::getHWCalloutFields(
452     const std::string& inventoryPath, std::string& fruPartNumber,
453     std::string& ccin, std::string& serialNumber) const
454 {
455     // For now, attempt to get all of the properties directly on the path
456     // passed in.  In the future, may need to make use of an algorithm
457     // to figure out which inventory objects actually hold these
458     // interfaces in the case of non FRUs, or possibly another service
459     // will provide this info.  Any missing interfaces will result
460     // in exceptions being thrown.
461 
462     auto service = getService(inventoryPath, interface::viniRecordVPD);
463 
464     auto properties =
465         getAllProperties(service, inventoryPath, interface::viniRecordVPD);
466 
467     auto value = std::get<std::vector<uint8_t>>(properties["FN"]);
468     fruPartNumber = std::string{value.begin(), value.end()};
469 
470     value = std::get<std::vector<uint8_t>>(properties["CC"]);
471     ccin = std::string{value.begin(), value.end()};
472 
473     value = std::get<std::vector<uint8_t>>(properties["SN"]);
474     serialNumber = std::string{value.begin(), value.end()};
475 }
476 
477 std::string
478     DataInterface::getLocationCode(const std::string& inventoryPath) const
479 {
480     auto service = getService(inventoryPath, interface::locCode);
481 
482     DBusValue locCode;
483     getProperty(service, inventoryPath, interface::locCode, "LocationCode",
484                 locCode);
485 
486     return std::get<std::string>(locCode);
487 }
488 
489 std::string
490     DataInterface::addLocationCodePrefix(const std::string& locationCode)
491 {
492     static const std::string locationCodePrefix{"Ufcs-"};
493 
494     // Technically there are 2 location code prefixes, Ufcs and Umts, so
495     // if it already starts with a U then don't need to do anything.
496     if (locationCode.front() != 'U')
497     {
498         return locationCodePrefix + locationCode;
499     }
500 
501     return locationCode;
502 }
503 
504 std::string DataInterface::expandLocationCode(const std::string& locationCode,
505                                               uint16_t /*node*/) const
506 {
507     // Location codes for connectors are the location code of the FRU they are
508     // on, plus a '-Tx' segment.  Remove this last segment before expanding it
509     // and then add it back in afterwards.  This way, the connector doesn't have
510     // to be in the model just so that it can be expanded.
511     auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode);
512 
513     auto method =
514         _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
515                              interface::vpdManager, "GetExpandedLocationCode");
516 
517     method.append(addLocationCodePrefix(baseLoc), static_cast<uint16_t>(0));
518 
519     auto reply = _bus.call(method, dbusTimeout);
520 
521     std::string expandedLocationCode;
522     reply.read(expandedLocationCode);
523 
524     if (!connectorLoc.empty())
525     {
526         expandedLocationCode += connectorLoc;
527     }
528 
529     return expandedLocationCode;
530 }
531 
532 std::vector<std::string> DataInterface::getInventoryFromLocCode(
533     const std::string& locationCode, uint16_t node, bool expanded) const
534 {
535     std::string methodName = expanded ? "GetFRUsByExpandedLocationCode"
536                                       : "GetFRUsByUnexpandedLocationCode";
537 
538     // Remove the connector segment, if present, so that this method call
539     // returns an inventory path that getHWCalloutFields() can be used with.
540     // (The serial number, etc, aren't stored on the connector in the
541     // inventory, and may not even be modeled.)
542     auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode);
543 
544     auto method =
545         _bus.new_method_call(service_name::vpdManager, object_path::vpdManager,
546                              interface::vpdManager, methodName.c_str());
547 
548     if (expanded)
549     {
550         method.append(baseLoc);
551     }
552     else
553     {
554         method.append(addLocationCodePrefix(baseLoc), node);
555     }
556 
557     auto reply = _bus.call(method, dbusTimeout);
558 
559     std::vector<sdbusplus::message::object_path> entries;
560     reply.read(entries);
561 
562     std::vector<std::string> paths;
563 
564     // Note: The D-Bus method will fail if nothing found.
565     std::for_each(entries.begin(), entries.end(),
566                   [&paths](const auto& path) { paths.push_back(path); });
567 
568     return paths;
569 }
570 
571 void DataInterface::assertLEDGroup(const std::string& ledGroup,
572                                    bool value) const
573 {
574     DBusValue variant = value;
575     auto method =
576         _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(),
577                              interface::dbusProperty, "Set");
578     method.append(interface::ledGroup, "Asserted", variant);
579     _bus.call(method, dbusTimeout);
580 }
581 
582 void DataInterface::setFunctional(const std::string& objectPath,
583                                   bool value) const
584 {
585     DBusPropertyMap prop{{"Functional", value}};
586     DBusInterfaceMap iface{{interface::operationalStatus, prop}};
587 
588     // PIM takes a relative path like /system/chassis so remove
589     // /xyz/openbmc_project/inventory if present.
590     std::string path{objectPath};
591     if (path.starts_with(object_path::baseInv))
592     {
593         path = objectPath.substr(strlen(object_path::baseInv));
594     }
595     DBusObjectMap object{{path, iface}};
596 
597     auto method = _bus.new_method_call(service_name::inventoryManager,
598                                        object_path::baseInv,
599                                        interface::inventoryManager, "Notify");
600     method.append(std::move(object));
601     _bus.call(method, dbusTimeout);
602 }
603 
604 using AssociationTuple = std::tuple<std::string, std::string, std::string>;
605 using AssociationsProperty = std::vector<AssociationTuple>;
606 
607 void DataInterface::setCriticalAssociation(const std::string& objectPath) const
608 {
609     DBusValue getAssociationValue;
610 
611     auto service = getService(objectPath, interface::associationDef);
612 
613     getProperty(service, objectPath, interface::associationDef, "Associations",
614                 getAssociationValue);
615 
616     auto association = std::get<AssociationsProperty>(getAssociationValue);
617 
618     AssociationTuple critAssociation{
619         "health_rollup", "critical",
620         "/xyz/openbmc_project/inventory/system/chassis"};
621 
622     if (std::find(association.begin(), association.end(), critAssociation) ==
623         association.end())
624     {
625         association.push_back(critAssociation);
626         DBusValue setAssociationValue = association;
627 
628         auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(),
629                                            interface::dbusProperty, "Set");
630 
631         method.append(interface::associationDef, "Associations",
632                       setAssociationValue);
633         _bus.call(method, dbusTimeout);
634     }
635 }
636 
637 std::vector<std::string> DataInterface::getSystemNames() const
638 {
639     DBusSubTree subtree;
640     DBusValue names;
641 
642     auto method = _bus.new_method_call(service_name::objectMapper,
643                                        object_path::objectMapper,
644                                        interface::objectMapper, "GetSubTree");
645     method.append(std::string{"/"}, 0,
646                   std::vector<std::string>{interface::compatible});
647     auto reply = _bus.call(method, dbusTimeout);
648 
649     reply.read(subtree);
650     if (subtree.empty())
651     {
652         throw std::runtime_error("Compatible interface not on D-Bus");
653     }
654 
655     for (const auto& [path, interfaceMap] : subtree)
656     {
657         auto iface = interfaceMap.find(service_name::entityManager);
658         if (iface == interfaceMap.end())
659         {
660             continue;
661         }
662 
663         getProperty(iface->first, path, interface::compatible, "Names", names);
664 
665         return std::get<std::vector<std::string>>(names);
666     }
667 
668     throw std::runtime_error("EM Compatible interface not on D-Bus");
669 }
670 
671 bool DataInterface::getQuiesceOnError() const
672 {
673     bool ret = false;
674 
675     try
676     {
677         auto service =
678             getService(object_path::logSetting, interface::logSetting);
679         if (!service.empty())
680         {
681             DBusValue value;
682             getProperty(service, object_path::logSetting, interface::logSetting,
683                         "QuiesceOnHwError", value);
684 
685             ret = std::get<bool>(value);
686         }
687     }
688     catch (const std::exception& e)
689     {
690         lg2::warning("Failed reading QuiesceOnHwError property from "
691                      "interface: {IFACE} exception: {ERROR}",
692                      "IFACE", interface::logSetting, "ERROR", e);
693     }
694 
695     return ret;
696 }
697 
698 #ifdef PEL_ENABLE_PHAL
699 void DataInterface::createGuardRecord(const std::vector<uint8_t>& binPath,
700                                       GardType eGardType, uint32_t plid) const
701 {
702     try
703     {
704         libguard::create(binPath, plid, eGardType);
705     }
706     catch (libguard::exception::GuardException& e)
707     {
708         lg2::error("Exception to create the guard {ERROR}", "ERROR", e.what());
709     }
710 }
711 #endif
712 
713 void DataInterface::createProgressSRC(
714     const uint64_t& priSRC, const std::vector<uint8_t>& srcStruct) const
715 {
716     DBusValue variant = std::make_tuple(priSRC, srcStruct);
717 
718     auto method = _bus.new_method_call(service_name::bootRawProgress,
719                                        object_path::bootRawProgress,
720                                        interface::dbusProperty, "Set");
721 
722     method.append(interface::bootRawProgress, "Value", variant);
723 
724     _bus.call(method, dbusTimeout);
725 }
726 
727 std::vector<uint32_t> DataInterface::getLogIDWithHwIsolation() const
728 {
729     std::vector<std::string> association = {"xyz.openbmc_project.Association"};
730     std::string hwErrorLog = "/isolated_hw_errorlog";
731     std::string errorLog = "/error_log";
732     DBusPathList paths;
733     std::vector<uint32_t> ids;
734 
735     // Get all latest mapper associations
736     paths = getPaths(association);
737     for (auto& path : paths)
738     {
739         // Look for object path with hardware isolation entry if any
740         size_t pos = path.find(hwErrorLog);
741         if (pos != std::string::npos)
742         {
743             // Get the object path
744             std::string ph = path;
745             ph.erase(pos, hwErrorLog.length());
746             auto service = getService(ph, interface::hwIsolationEntry);
747             if (!service.empty())
748             {
749                 bool status;
750                 DBusValue value;
751 
752                 // Read the Resolved property from object path
753                 getProperty(service, ph, interface::hwIsolationEntry,
754                             "Resolved", value);
755 
756                 status = std::get<bool>(value);
757 
758                 // If the entry isn't resolved
759                 if (!status)
760                 {
761                     auto assocService =
762                         getService(path, interface::association);
763                     if (!assocService.empty())
764                     {
765                         DBusValue endpoints;
766 
767                         // Read Endpoints property
768                         getProperty(assocService, path, interface::association,
769                                     "endpoints", endpoints);
770 
771                         auto logPath =
772                             std::get<std::vector<std::string>>(endpoints);
773                         if (!logPath.empty())
774                         {
775                             // Get OpenBMC event log Id
776                             uint32_t id = stoi(logPath[0].substr(
777                                 logPath[0].find_last_of('/') + 1));
778                             ids.push_back(id);
779                         }
780                     }
781                 }
782             }
783         }
784 
785         // Look for object path with error_log entry if any
786         pos = path.find(errorLog);
787         if (pos != std::string::npos)
788         {
789             auto service = getService(path, interface::association);
790             if (!service.empty())
791             {
792                 DBusValue value;
793 
794                 // Read Endpoints property
795                 getProperty(service, path, interface::association, "endpoints",
796                             value);
797 
798                 auto logPath = std::get<std::vector<std::string>>(value);
799                 if (!logPath.empty())
800                 {
801                     // Get OpenBMC event log Id
802                     uint32_t id = stoi(
803                         logPath[0].substr(logPath[0].find_last_of('/') + 1));
804                     ids.push_back(id);
805                 }
806             }
807         }
808     }
809 
810     if (ids.size() > 1)
811     {
812         // remove duplicates to have only unique ids
813         std::sort(ids.begin(), ids.end());
814         ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
815     }
816     return ids;
817 }
818 
819 std::vector<uint8_t> DataInterface::getRawProgressSRC(void) const
820 {
821     using RawProgressProperty = std::tuple<uint64_t, std::vector<uint8_t>>;
822 
823     DBusValue value;
824     getProperty(service_name::bootRawProgress, object_path::bootRawProgress,
825                 interface::bootRawProgress, "Value", value);
826 
827     const auto& rawProgress = std::get<RawProgressProperty>(value);
828     return std::get<1>(rawProgress);
829 }
830 
831 std::optional<std::vector<uint8_t>>
832     DataInterface::getDIProperty(const std::string& locationCode) const
833 {
834     std::vector<uint8_t> viniDI;
835 
836     try
837     {
838         // Note : The hardcoded value 0 should be changed when comes to
839         // multinode system.
840         auto objectPath = getInventoryFromLocCode(locationCode, 0, true);
841 
842         DBusValue value;
843         getProperty(service_name::inventoryManager, objectPath[0],
844                     interface::viniRecordVPD, "DI", value);
845 
846         viniDI = std::get<std::vector<uint8_t>>(value);
847     }
848     catch (const std::exception& e)
849     {
850         lg2::warning(
851             "Failed reading DI property for the location code : {LOC_CODE} from "
852             "interface: {IFACE} exception: {ERROR}",
853             "LOC_CODE", locationCode, "IFACE", interface::viniRecordVPD,
854             "ERROR", e);
855         return std::nullopt;
856     }
857 
858     return viniDI;
859 }
860 
861 std::optional<bool>
862     DataInterfaceBase::isDIMMLocCode(const std::string& locCode) const
863 {
864     if (_locationCache.contains(locCode))
865     {
866         return _locationCache.at(locCode);
867     }
868     else
869     {
870         return std::nullopt;
871     }
872 }
873 
874 void DataInterfaceBase::addDIMMLocCode(const std::string& locCode,
875                                        bool isFRUDIMM)
876 {
877     _locationCache.insert({locCode, isFRUDIMM});
878 }
879 
880 bool DataInterfaceBase::isDIMM(const std::string& locCode)
881 {
882     auto isDIMMType = isDIMMLocCode(locCode);
883     if (isDIMMType.has_value())
884     {
885         return isDIMMType.value();
886     }
887 #ifndef PEL_ENABLE_PHAL
888     return false;
889 #else
890     else
891     {
892         // Invoke pHAL API inorder to fetch the FRU Type
893         auto fruType = openpower::phal::pdbg::getFRUType(locCode);
894         bool isDIMMFRU{false};
895         if (fruType.has_value())
896         {
897             if (fruType.value() == ENUM_ATTR_TYPE_DIMM)
898             {
899                 isDIMMFRU = true;
900             }
901             addDIMMLocCode(locCode, isDIMMFRU);
902         }
903         return isDIMMFRU;
904     }
905 #endif
906 }
907 
908 DBusPathList DataInterface::getAssociatedPaths(
909     const DBusPath& associatedPath, const DBusPath& subtree, int32_t depth,
910     const DBusInterfaceList& interfaces) const
911 {
912     DBusPathList paths;
913     try
914     {
915         auto method = _bus.new_method_call(
916             service_name::objectMapper, object_path::objectMapper,
917             interface::objectMapper, "GetAssociatedSubTreePaths");
918         method.append(sdbusplus::message::object_path(associatedPath),
919                       sdbusplus::message::object_path(subtree), depth,
920                       interfaces);
921 
922         auto reply = _bus.call(method, dbusTimeout);
923         reply.read(paths);
924     }
925     catch (const std::exception& e)
926     {
927         std::string ifaces(
928             std::ranges::fold_left_first(
929                 interfaces,
930                 [](std::string ifaces, const std::string& iface) {
931                     return ifaces + ", " + iface;
932                 })
933                 .value_or(""));
934 
935         lg2::error("Failed getting associated paths: {ERROR}. "
936                    "AssociatedPath: {ASSOIC_PATH} Subtree: {SUBTREE} "
937                    "Interfaces: {IFACES}",
938                    "ERROR", e, "ASSOIC_PATH", associatedPath, "SUBTREE",
939                    subtree, "IFACES", ifaces);
940     }
941     return paths;
942 }
943 
944 void DataInterface::startFruPlugWatch()
945 {
946     // Add a watch on inventory InterfacesAdded and then find all
947     // existing hotpluggable interfaces and add propertiesChanged
948     // watches on them.
949 
950     _invIaMatch = std::make_unique<sdbusplus::bus::match_t>(
951         _bus, match_rules::interfacesAdded(object_path::baseInv),
952         std::bind(&DataInterface::inventoryIfaceAdded, this,
953                   std::placeholders::_1));
954     try
955     {
956         auto paths = getPaths(hotplugInterfaces);
957 
958         _invPresentMatches.clear();
959 
960         std::for_each(paths.begin(), paths.end(),
961                       [this](const auto& path) { addHotplugWatch(path); });
962     }
963     catch (const sdbusplus::exception_t& e)
964     {
965         lg2::warning("Failed getting FRU paths to watch: {ERROR}", "ERROR", e);
966     }
967 }
968 
969 void DataInterface::addHotplugWatch(const std::string& path)
970 {
971     if (!_invPresentMatches.contains(path))
972     {
973         _invPresentMatches.emplace(
974             path,
975             std::make_unique<sdbusplus::bus::match_t>(
976                 _bus, match_rules::propertiesChanged(path, interface::invItem),
977                 std::bind(&DataInterface::presenceChanged, this,
978                           std::placeholders::_1)));
979     }
980 }
981 
982 void DataInterface::inventoryIfaceAdded(sdbusplus::message_t& msg)
983 {
984     sdbusplus::message::object_path path;
985     DBusInterfaceMap interfaces;
986 
987     msg.read(path, interfaces);
988 
989     // Check if any of the new interfaces are for hot pluggable FRUs.
990     if (std::find_if(interfaces.begin(), interfaces.end(),
991                      [](const auto& interfacePair) {
992                          return std::find(hotplugInterfaces.begin(),
993                                           hotplugInterfaces.end(),
994                                           interfacePair.first) !=
995                                 hotplugInterfaces.end();
996                      }) == interfaces.end())
997     {
998         return;
999     }
1000 
1001     addHotplugWatch(path.str);
1002 
1003     // If an Inventory.Item interface was also added, check presence now.
1004 
1005     // Notes:
1006     // * This assumes the Inv.Item and Inv.Fan/PS are added together which
1007     //   is currently the case.
1008     // * If the code ever switches to something without a Present
1009     //   property, then the IA signal itself would probably indicate presence.
1010 
1011     auto itemIt = interfaces.find(interface::invItem);
1012     if (itemIt != interfaces.end())
1013     {
1014         notifyPresenceSubsribers(path.str, itemIt->second);
1015     }
1016 }
1017 
1018 void DataInterface::presenceChanged(sdbusplus::message_t& msg)
1019 {
1020     DBusInterface interface;
1021     DBusPropertyMap properties;
1022 
1023     msg.read(interface, properties);
1024     if (interface != interface::invItem)
1025     {
1026         return;
1027     }
1028 
1029     std::string path = msg.get_path();
1030     notifyPresenceSubsribers(path, properties);
1031 }
1032 
1033 void DataInterface::notifyPresenceSubsribers(const std::string& path,
1034                                              const DBusPropertyMap& properties)
1035 {
1036     auto prop = properties.find("Present");
1037     if ((prop == properties.end()) || (!std::get<bool>(prop->second)))
1038     {
1039         return;
1040     }
1041 
1042     std::string locCode;
1043 
1044     try
1045     {
1046         auto service = getService(path, interface::locCode);
1047 
1048         // If the hotplugged FRU is hosted by PLDM, then it is
1049         // in an IO expansion drawer and we don't care about it.
1050         if (service == service_name::pldm)
1051         {
1052             return;
1053         }
1054 
1055         locCode = getLocationCode(path);
1056     }
1057     catch (const sdbusplus::exception_t& e)
1058     {
1059         lg2::debug("Could not get location code for {PATH}: {ERROR}", "PATH",
1060                    path, "ERROR", e);
1061         return;
1062     }
1063 
1064     lg2::debug("Detected FRU {PATH} ({LOC}) present ", "PATH", path, "LOC",
1065                locCode);
1066 
1067     // Tell the subscribers.
1068     setFruPresent(locCode);
1069 }
1070 
1071 bool DataInterface::isPHALDevTreeExist() const
1072 {
1073     try
1074     {
1075         if (std::filesystem::exists(PDBG_DTB_PATH))
1076         {
1077             return true;
1078         }
1079     }
1080     catch (const std::exception& e)
1081     {
1082         lg2::error("Failed to check device tree {PHAL_DEVTREE_PATH} existence, "
1083                    "{ERROR}",
1084                    "PHAL_DEVTREE_PATH", PDBG_DTB_PATH, "ERROR", e);
1085     }
1086     return false;
1087 }
1088 
1089 #ifdef PEL_ENABLE_PHAL
1090 void DataInterface::initPHAL()
1091 {
1092     if (setenv("PDBG_DTB", PDBG_DTB_PATH, 1))
1093     {
1094         // Log message and continue,
1095         // This is to help continue creating PEL in raw format.
1096         lg2::error("Failed to set PDBG_DTB: ({ERRNO})", "ERRNO",
1097                    strerror(errno));
1098     }
1099 
1100     if (!pdbg_targets_init(NULL))
1101     {
1102         lg2::error("pdbg_targets_init failed");
1103         return;
1104     }
1105 
1106     if (libekb_init())
1107     {
1108         lg2::error("libekb_init failed, skipping ffdc processing");
1109         return;
1110     }
1111 }
1112 #endif
1113 
1114 void DataInterface::subscribeToSystemdSignals()
1115 {
1116     try
1117     {
1118         auto method =
1119             _bus.new_method_call(service_name::systemd, object_path::systemd,
1120                                  interface::systemdMgr, "Subscribe");
1121         _systemdSlot = method.call_async([this](sdbusplus::message_t&& msg) {
1122             // Initializing with nullptr to indicate that it is not subscribed
1123             // to any signal.
1124             this->_systemdSlot = sdbusplus::slot_t(nullptr);
1125             if (msg.is_method_error())
1126             {
1127                 auto* error = msg.get_error();
1128                 lg2::error("Failed to subscribe JobRemoved systemd signal, "
1129                            "errorName: {ERR_NAME}, errorMsg: {ERR_MSG} ",
1130                            "ERR_NAME", error->name, "ERR_MSG", error->message);
1131                 return;
1132             }
1133 
1134             namespace sdbusRule = sdbusplus::bus::match::rules;
1135             this->_systemdMatch =
1136                 std::make_unique<decltype(this->_systemdMatch)::element_type>(
1137                     this->_bus,
1138                     sdbusRule::type::signal() +
1139                         sdbusRule::member("JobRemoved") +
1140                         sdbusRule::path(object_path::systemd) +
1141                         sdbusRule::interface(interface::systemdMgr),
1142                     [this](sdbusplus::message_t& msg) {
1143                         uint32_t jobID;
1144                         sdbusplus::message::object_path jobObjPath;
1145                         std::string jobUnitName, jobUnitResult;
1146 
1147                         msg.read(jobID, jobObjPath, jobUnitName, jobUnitResult);
1148                         if ((jobUnitName ==
1149                              "openpower-update-bios-attr-table.service") &&
1150                             (jobUnitResult == "done"))
1151                         {
1152 #ifdef PEL_ENABLE_PHAL
1153                             this->initPHAL();
1154 #endif
1155                             // Invoke unsubscribe method to stop monitoring for
1156                             // JobRemoved signals.
1157                             this->unsubscribeFromSystemdSignals();
1158                         }
1159                     });
1160         });
1161     }
1162     catch (const sdbusplus::exception_t& e)
1163     {
1164         lg2::error(
1165             "Exception occured while handling JobRemoved systemd signal, "
1166             "exception: {ERROR}",
1167             "ERROR", e);
1168     }
1169 }
1170 
1171 void DataInterface::unsubscribeFromSystemdSignals()
1172 {
1173     try
1174     {
1175         auto method =
1176             _bus.new_method_call(service_name::systemd, object_path::systemd,
1177                                  interface::systemdMgr, "Unsubscribe");
1178         _systemdSlot = method.call_async([this](sdbusplus::message_t&& msg) {
1179             // Unsubscribing the _systemdSlot from the subscribed signal
1180             this->_systemdSlot = sdbusplus::slot_t(nullptr);
1181             if (msg.is_method_error())
1182             {
1183                 auto* error = msg.get_error();
1184                 lg2::error(
1185                     "Failed to unsubscribe from JobRemoved systemd signal, "
1186                     "errorName: {ERR_NAME}, errorMsg: {ERR_MSG} ",
1187                     "ERR_NAME", error->name, "ERR_MSG", error->message);
1188                 return;
1189             }
1190             // Reset _systemdMatch to avoid reception of further JobRemoved
1191             // signals
1192             this->_systemdMatch.reset();
1193         });
1194     }
1195     catch (const sdbusplus::exception_t& e)
1196     {
1197         lg2::error(
1198             "Exception occured while unsubscribing from JobRemoved systemd signal, "
1199             "exception: {ERROR}",
1200             "ERROR", e);
1201     }
1202 }
1203 
1204 } // namespace pels
1205 } // namespace openpower
1206