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