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