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