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