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>(¤tValVariant);
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