| /** |
| * Copyright © 2019 IBM Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "data_interface.hpp" |
| |
| #include "util.hpp" |
| |
| #include <phosphor-logging/lg2.hpp> |
| #include <xyz/openbmc_project/State/BMC/server.hpp> |
| #include <xyz/openbmc_project/State/Boot/Progress/server.hpp> |
| |
| #include <filesystem> |
| |
| #ifdef PEL_ENABLE_PHAL |
| #include <libekb.H> |
| #include <libpdbg.h> |
| #include <libphal.H> |
| #endif |
| |
| // Use a timeout of 10s for D-Bus calls so if there are |
| // timeouts the callers of the PEL creation method won't |
| // also timeout. |
| constexpr auto dbusTimeout = 10000000; |
| |
| namespace openpower |
| { |
| namespace pels |
| { |
| |
| namespace service_name |
| { |
| constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper"; |
| constexpr auto vpdManager = "com.ibm.VPD.Manager"; |
| constexpr auto ledGroupManager = "xyz.openbmc_project.LED.GroupManager"; |
| constexpr auto hwIsolation = "org.open_power.HardwareIsolation"; |
| constexpr auto biosConfigMgr = "xyz.openbmc_project.BIOSConfigManager"; |
| constexpr auto bootRawProgress = "xyz.openbmc_project.State.Boot.Raw"; |
| constexpr auto pldm = "xyz.openbmc_project.PLDM"; |
| constexpr auto inventoryManager = "xyz.openbmc_project.Inventory.Manager"; |
| constexpr auto entityManager = "xyz.openbmc_project.EntityManager"; |
| constexpr auto systemd = "org.freedesktop.systemd1"; |
| } // namespace service_name |
| |
| namespace object_path |
| { |
| constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper"; |
| constexpr auto systemInv = "/xyz/openbmc_project/inventory/system"; |
| constexpr auto motherBoardInv = |
| "/xyz/openbmc_project/inventory/system/chassis/motherboard"; |
| constexpr auto baseInv = "/xyz/openbmc_project/inventory"; |
| constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0"; |
| constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0"; |
| constexpr auto hostState = "/xyz/openbmc_project/state/host0"; |
| constexpr auto enableHostPELs = |
| "/xyz/openbmc_project/logging/send_event_logs_to_host"; |
| constexpr auto vpdManager = "/com/ibm/VPD/Manager"; |
| constexpr auto logSetting = "/xyz/openbmc_project/logging/settings"; |
| constexpr auto hwIsolation = "/xyz/openbmc_project/hardware_isolation"; |
| constexpr auto biosConfigMgr = "/xyz/openbmc_project/bios_config/manager"; |
| constexpr auto bootRawProgress = "/xyz/openbmc_project/state/boot/raw0"; |
| constexpr auto systemd = "/org/freedesktop/systemd1"; |
| } // namespace object_path |
| |
| namespace interface |
| { |
| constexpr auto dbusProperty = "org.freedesktop.DBus.Properties"; |
| constexpr auto objectMapper = "xyz.openbmc_project.ObjectMapper"; |
| constexpr auto invAsset = "xyz.openbmc_project.Inventory.Decorator.Asset"; |
| constexpr auto bootProgress = "xyz.openbmc_project.State.Boot.Progress"; |
| constexpr auto enable = "xyz.openbmc_project.Object.Enable"; |
| constexpr auto bmcState = "xyz.openbmc_project.State.BMC"; |
| constexpr auto chassisState = "xyz.openbmc_project.State.Chassis"; |
| constexpr auto hostState = "xyz.openbmc_project.State.Host"; |
| constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI"; |
| constexpr auto vsbpRecordVPD = "com.ibm.ipzvpd.VSBP"; |
| constexpr auto locCode = "xyz.openbmc_project.Inventory.Decorator.LocationCode"; |
| constexpr auto compatible = |
| "xyz.openbmc_project.Inventory.Decorator.Compatible"; |
| constexpr auto vpdManager = "com.ibm.VPD.Manager"; |
| constexpr auto ledGroup = "xyz.openbmc_project.Led.Group"; |
| constexpr auto operationalStatus = |
| "xyz.openbmc_project.State.Decorator.OperationalStatus"; |
| constexpr auto logSetting = "xyz.openbmc_project.Logging.Settings"; |
| constexpr auto associationDef = "xyz.openbmc_project.Association.Definitions"; |
| constexpr auto dumpEntry = "xyz.openbmc_project.Dump.Entry"; |
| constexpr auto dumpProgress = "xyz.openbmc_project.Common.Progress"; |
| constexpr auto hwIsolationCreate = "org.open_power.HardwareIsolation.Create"; |
| constexpr auto hwIsolationEntry = "xyz.openbmc_project.HardwareIsolation.Entry"; |
| constexpr auto association = "xyz.openbmc_project.Association"; |
| constexpr auto biosConfigMgr = "xyz.openbmc_project.BIOSConfig.Manager"; |
| constexpr auto bootRawProgress = "xyz.openbmc_project.State.Boot.Raw"; |
| constexpr auto invItem = "xyz.openbmc_project.Inventory.Item"; |
| constexpr auto invFan = "xyz.openbmc_project.Inventory.Item.Fan"; |
| constexpr auto invPowerSupply = |
| "xyz.openbmc_project.Inventory.Item.PowerSupply"; |
| constexpr auto inventoryManager = "xyz.openbmc_project.Inventory.Manager"; |
| constexpr auto systemdMgr = "org.freedesktop.systemd1.Manager"; |
| } // namespace interface |
| |
| using namespace sdbusplus::server::xyz::openbmc_project::state::boot; |
| using namespace sdbusplus::server::xyz::openbmc_project::state; |
| namespace match_rules = sdbusplus::bus::match::rules; |
| |
| const DBusInterfaceList hotplugInterfaces{interface::invFan, |
| interface::invPowerSupply}; |
| static constexpr auto PDBG_DTB_PATH = |
| "/var/lib/phosphor-software-manager/hostfw/running/DEVTREE"; |
| |
| std::pair<std::string, std::string> |
| DataInterfaceBase::extractConnectorFromLocCode( |
| const std::string& locationCode) |
| { |
| auto base = locationCode; |
| std::string connector{}; |
| |
| auto pos = base.find("-T"); |
| if (pos != std::string::npos) |
| { |
| connector = base.substr(pos); |
| base = base.substr(0, pos); |
| } |
| |
| return {base, connector}; |
| } |
| |
| DataInterface::DataInterface(sdbusplus::bus_t& bus) : |
| _bus(bus), _systemdSlot(nullptr) |
| { |
| readBMCFWVersion(); |
| readServerFWVersion(); |
| readBMCFWVersionID(); |
| |
| // Watch the BootProgress property |
| _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( |
| bus, object_path::hostState, interface::bootProgress, "BootProgress", |
| *this, [this](const auto& value) { |
| this->_bootState = std::get<std::string>(value); |
| auto status = Progress::convertProgressStagesFromString( |
| std::get<std::string>(value)); |
| |
| if ((status == Progress::ProgressStages::SystemInitComplete) || |
| (status == Progress::ProgressStages::OSRunning)) |
| { |
| setHostUp(true); |
| } |
| else |
| { |
| setHostUp(false); |
| } |
| })); |
| |
| // Watch the host PEL enable property |
| _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( |
| bus, object_path::enableHostPELs, interface::enable, "Enabled", *this, |
| [this](const auto& value) { |
| if (std::get<bool>(value) != this->_sendPELsToHost) |
| { |
| lg2::info("The send PELs to host setting changed to {VAL}", |
| "VAL", std::get<bool>(value)); |
| } |
| this->_sendPELsToHost = std::get<bool>(value); |
| })); |
| |
| // Watch the BMCState property |
| _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( |
| bus, object_path::bmcState, interface::bmcState, "CurrentBMCState", |
| *this, [this](const auto& value) { |
| const auto& state = std::get<std::string>(value); |
| this->_bmcState = state; |
| |
| // Wait for BMC ready to start watching for |
| // plugs so things calm down first. |
| if (BMC::convertBMCStateFromString(state) == BMC::BMCState::Ready) |
| { |
| startFruPlugWatch(); |
| } |
| })); |
| |
| // Watch the chassis current and requested power state properties |
| _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>( |
| bus, object_path::chassisState, interface::chassisState, *this, |
| [this](const auto& properties) { |
| auto state = properties.find("CurrentPowerState"); |
| if (state != properties.end()) |
| { |
| this->_chassisState = std::get<std::string>(state->second); |
| } |
| |
| auto trans = properties.find("RequestedPowerTransition"); |
| if (trans != properties.end()) |
| { |
| this->_chassisTransition = std::get<std::string>(trans->second); |
| } |
| })); |
| |
| // Watch the CurrentHostState property |
| _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( |
| bus, object_path::hostState, interface::hostState, "CurrentHostState", |
| *this, [this](const auto& value) { |
| this->_hostState = std::get<std::string>(value); |
| })); |
| |
| // Watch the BaseBIOSTable property for the hmc managed attribute |
| _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( |
| bus, object_path::biosConfigMgr, interface::biosConfigMgr, |
| "BaseBIOSTable", service_name::biosConfigMgr, *this, |
| [this](const auto& value) { |
| const auto& attributes = std::get<BiosAttributes>(value); |
| |
| auto it = attributes.find("pvm_hmc_managed"); |
| if (it != attributes.end()) |
| { |
| const auto& currentValVariant = std::get<5>(it->second); |
| auto currentVal = std::get_if<std::string>(¤tValVariant); |
| if (currentVal) |
| { |
| this->_hmcManaged = |
| (*currentVal == "Enabled") ? true : false; |
| } |
| } |
| })); |
| |
| if (isPHALDevTreeExist()) |
| { |
| #ifdef PEL_ENABLE_PHAL |
| initPHAL(); |
| #endif |
| } |
| else |
| { |
| // Watch the "openpower-update-bios-attr-table" service to init |
| // PHAL libraries |
| subscribeToSystemdSignals(); |
| } |
| } |
| |
| DBusPropertyMap DataInterface::getAllProperties( |
| const std::string& service, const std::string& objectPath, |
| const std::string& interface) const |
| { |
| DBusPropertyMap properties; |
| |
| auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), |
| interface::dbusProperty, "GetAll"); |
| method.append(interface); |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| reply.read(properties); |
| |
| return properties; |
| } |
| |
| void DataInterface::getProperty( |
| const std::string& service, const std::string& objectPath, |
| const std::string& interface, const std::string& property, |
| DBusValue& value) const |
| { |
| auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), |
| interface::dbusProperty, "Get"); |
| method.append(interface, property); |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| reply.read(value); |
| } |
| |
| DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const |
| { |
| auto method = _bus.new_method_call( |
| service_name::objectMapper, object_path::objectMapper, |
| interface::objectMapper, "GetSubTreePaths"); |
| |
| method.append(std::string{"/"}, 0, interfaces); |
| |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| DBusPathList paths; |
| reply.read(paths); |
| |
| return paths; |
| } |
| |
| DBusService DataInterface::getService(const std::string& objectPath, |
| const std::string& interface) const |
| { |
| auto method = _bus.new_method_call(service_name::objectMapper, |
| object_path::objectMapper, |
| interface::objectMapper, "GetObject"); |
| |
| method.append(objectPath, std::vector<std::string>({interface})); |
| |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| std::map<DBusService, DBusInterfaceList> response; |
| reply.read(response); |
| |
| if (!response.empty()) |
| { |
| return response.begin()->first; |
| } |
| |
| return std::string{}; |
| } |
| |
| void DataInterface::readBMCFWVersion() |
| { |
| _bmcFWVersion = |
| phosphor::logging::util::getOSReleaseValue("VERSION").value_or(""); |
| } |
| |
| void DataInterface::readServerFWVersion() |
| { |
| auto value = |
| phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or(""); |
| if ((value != "") && (value.find_last_of(')') != std::string::npos)) |
| { |
| std::size_t pos = value.find_first_of('(') + 1; |
| _serverFWVersion = value.substr(pos, value.find_last_of(')') - pos); |
| } |
| } |
| |
| void DataInterface::readBMCFWVersionID() |
| { |
| _bmcFWVersionID = |
| phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or(""); |
| } |
| |
| std::string DataInterface::getMachineTypeModel() const |
| { |
| std::string model; |
| try |
| { |
| auto service = getService(object_path::systemInv, interface::invAsset); |
| if (!service.empty()) |
| { |
| DBusValue value; |
| getProperty(service, object_path::systemInv, interface::invAsset, |
| "Model", value); |
| |
| model = std::get<std::string>(value); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::warning("Failed reading Model property from " |
| "interface: {IFACE} exception: {ERROR}", |
| "IFACE", interface::invAsset, "ERROR", e); |
| } |
| |
| return model; |
| } |
| |
| std::string DataInterface::getMachineSerialNumber() const |
| { |
| std::string sn; |
| try |
| { |
| auto service = getService(object_path::systemInv, interface::invAsset); |
| if (!service.empty()) |
| { |
| DBusValue value; |
| getProperty(service, object_path::systemInv, interface::invAsset, |
| "SerialNumber", value); |
| |
| sn = std::get<std::string>(value); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::warning("Failed reading SerialNumber property from " |
| "interface: {IFACE} exception: {ERROR}", |
| "IFACE", interface::invAsset, "ERROR", e); |
| } |
| |
| return sn; |
| } |
| |
| std::string DataInterface::getMotherboardCCIN() const |
| { |
| std::string ccin; |
| |
| try |
| { |
| auto service = |
| getService(object_path::motherBoardInv, interface::viniRecordVPD); |
| if (!service.empty()) |
| { |
| DBusValue value; |
| getProperty(service, object_path::motherBoardInv, |
| interface::viniRecordVPD, "CC", value); |
| |
| auto cc = std::get<std::vector<uint8_t>>(value); |
| ccin = std::string{cc.begin(), cc.end()}; |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::warning("Failed reading Motherboard CCIN property from " |
| "interface: {IFACE} exception: {ERROR}", |
| "IFACE", interface::viniRecordVPD, "ERROR", e); |
| } |
| |
| return ccin; |
| } |
| |
| std::vector<uint8_t> DataInterface::getSystemIMKeyword() const |
| { |
| std::vector<uint8_t> systemIM; |
| |
| try |
| { |
| auto service = |
| getService(object_path::motherBoardInv, interface::vsbpRecordVPD); |
| if (!service.empty()) |
| { |
| DBusValue value; |
| getProperty(service, object_path::motherBoardInv, |
| interface::vsbpRecordVPD, "IM", value); |
| |
| systemIM = std::get<std::vector<uint8_t>>(value); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::warning("Failed reading System IM property from " |
| "interface: {IFACE} exception: {ERROR}", |
| "IFACE", interface::vsbpRecordVPD, "ERROR", e); |
| } |
| |
| return systemIM; |
| } |
| |
| void DataInterface::getHWCalloutFields( |
| const std::string& inventoryPath, std::string& fruPartNumber, |
| std::string& ccin, std::string& serialNumber) const |
| { |
| // For now, attempt to get all of the properties directly on the path |
| // passed in. In the future, may need to make use of an algorithm |
| // to figure out which inventory objects actually hold these |
| // interfaces in the case of non FRUs, or possibly another service |
| // will provide this info. Any missing interfaces will result |
| // in exceptions being thrown. |
| |
| auto service = getService(inventoryPath, interface::viniRecordVPD); |
| |
| auto properties = |
| getAllProperties(service, inventoryPath, interface::viniRecordVPD); |
| |
| auto value = std::get<std::vector<uint8_t>>(properties["FN"]); |
| fruPartNumber = std::string{value.begin(), value.end()}; |
| |
| value = std::get<std::vector<uint8_t>>(properties["CC"]); |
| ccin = std::string{value.begin(), value.end()}; |
| |
| value = std::get<std::vector<uint8_t>>(properties["SN"]); |
| serialNumber = std::string{value.begin(), value.end()}; |
| } |
| |
| std::string |
| DataInterface::getLocationCode(const std::string& inventoryPath) const |
| { |
| auto service = getService(inventoryPath, interface::locCode); |
| |
| DBusValue locCode; |
| getProperty(service, inventoryPath, interface::locCode, "LocationCode", |
| locCode); |
| |
| return std::get<std::string>(locCode); |
| } |
| |
| std::string |
| DataInterface::addLocationCodePrefix(const std::string& locationCode) |
| { |
| static const std::string locationCodePrefix{"Ufcs-"}; |
| |
| // Technically there are 2 location code prefixes, Ufcs and Umts, so |
| // if it already starts with a U then don't need to do anything. |
| if (locationCode.front() != 'U') |
| { |
| return locationCodePrefix + locationCode; |
| } |
| |
| return locationCode; |
| } |
| |
| std::string DataInterface::expandLocationCode(const std::string& locationCode, |
| uint16_t /*node*/) const |
| { |
| // Location codes for connectors are the location code of the FRU they are |
| // on, plus a '-Tx' segment. Remove this last segment before expanding it |
| // and then add it back in afterwards. This way, the connector doesn't have |
| // to be in the model just so that it can be expanded. |
| auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode); |
| |
| auto method = |
| _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, |
| interface::vpdManager, "GetExpandedLocationCode"); |
| |
| method.append(addLocationCodePrefix(baseLoc), static_cast<uint16_t>(0)); |
| |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| std::string expandedLocationCode; |
| reply.read(expandedLocationCode); |
| |
| if (!connectorLoc.empty()) |
| { |
| expandedLocationCode += connectorLoc; |
| } |
| |
| return expandedLocationCode; |
| } |
| |
| std::vector<std::string> DataInterface::getInventoryFromLocCode( |
| const std::string& locationCode, uint16_t node, bool expanded) const |
| { |
| std::string methodName = expanded ? "GetFRUsByExpandedLocationCode" |
| : "GetFRUsByUnexpandedLocationCode"; |
| |
| // Remove the connector segment, if present, so that this method call |
| // returns an inventory path that getHWCalloutFields() can be used with. |
| // (The serial number, etc, aren't stored on the connector in the |
| // inventory, and may not even be modeled.) |
| auto [baseLoc, connectorLoc] = extractConnectorFromLocCode(locationCode); |
| |
| auto method = |
| _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, |
| interface::vpdManager, methodName.c_str()); |
| |
| if (expanded) |
| { |
| method.append(baseLoc); |
| } |
| else |
| { |
| method.append(addLocationCodePrefix(baseLoc), node); |
| } |
| |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| std::vector<sdbusplus::message::object_path> entries; |
| reply.read(entries); |
| |
| std::vector<std::string> paths; |
| |
| // Note: The D-Bus method will fail if nothing found. |
| std::for_each(entries.begin(), entries.end(), |
| [&paths](const auto& path) { paths.push_back(path); }); |
| |
| return paths; |
| } |
| |
| void DataInterface::assertLEDGroup(const std::string& ledGroup, |
| bool value) const |
| { |
| DBusValue variant = value; |
| auto method = |
| _bus.new_method_call(service_name::ledGroupManager, ledGroup.c_str(), |
| interface::dbusProperty, "Set"); |
| method.append(interface::ledGroup, "Asserted", variant); |
| _bus.call(method, dbusTimeout); |
| } |
| |
| void DataInterface::setFunctional(const std::string& objectPath, |
| bool value) const |
| { |
| DBusPropertyMap prop{{"Functional", value}}; |
| DBusInterfaceMap iface{{interface::operationalStatus, prop}}; |
| |
| // PIM takes a relative path like /system/chassis so remove |
| // /xyz/openbmc_project/inventory if present. |
| std::string path{objectPath}; |
| if (path.starts_with(object_path::baseInv)) |
| { |
| path = objectPath.substr(strlen(object_path::baseInv)); |
| } |
| DBusObjectMap object{{path, iface}}; |
| |
| auto method = _bus.new_method_call(service_name::inventoryManager, |
| object_path::baseInv, |
| interface::inventoryManager, "Notify"); |
| method.append(std::move(object)); |
| _bus.call(method, dbusTimeout); |
| } |
| |
| using AssociationTuple = std::tuple<std::string, std::string, std::string>; |
| using AssociationsProperty = std::vector<AssociationTuple>; |
| |
| void DataInterface::setCriticalAssociation(const std::string& objectPath) const |
| { |
| DBusValue getAssociationValue; |
| |
| auto service = getService(objectPath, interface::associationDef); |
| |
| getProperty(service, objectPath, interface::associationDef, "Associations", |
| getAssociationValue); |
| |
| auto association = std::get<AssociationsProperty>(getAssociationValue); |
| |
| AssociationTuple critAssociation{ |
| "health_rollup", "critical", |
| "/xyz/openbmc_project/inventory/system/chassis"}; |
| |
| if (std::find(association.begin(), association.end(), critAssociation) == |
| association.end()) |
| { |
| association.push_back(critAssociation); |
| DBusValue setAssociationValue = association; |
| |
| auto method = _bus.new_method_call(service.c_str(), objectPath.c_str(), |
| interface::dbusProperty, "Set"); |
| |
| method.append(interface::associationDef, "Associations", |
| setAssociationValue); |
| _bus.call(method, dbusTimeout); |
| } |
| } |
| |
| std::vector<std::string> DataInterface::getSystemNames() const |
| { |
| DBusSubTree subtree; |
| DBusValue names; |
| |
| auto method = _bus.new_method_call(service_name::objectMapper, |
| object_path::objectMapper, |
| interface::objectMapper, "GetSubTree"); |
| method.append(std::string{"/"}, 0, |
| std::vector<std::string>{interface::compatible}); |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| reply.read(subtree); |
| if (subtree.empty()) |
| { |
| throw std::runtime_error("Compatible interface not on D-Bus"); |
| } |
| |
| for (const auto& [path, interfaceMap] : subtree) |
| { |
| auto iface = interfaceMap.find(service_name::entityManager); |
| if (iface == interfaceMap.end()) |
| { |
| continue; |
| } |
| |
| getProperty(iface->first, path, interface::compatible, "Names", names); |
| |
| return std::get<std::vector<std::string>>(names); |
| } |
| |
| throw std::runtime_error("EM Compatible interface not on D-Bus"); |
| } |
| |
| bool DataInterface::getQuiesceOnError() const |
| { |
| bool ret = false; |
| |
| try |
| { |
| auto service = |
| getService(object_path::logSetting, interface::logSetting); |
| if (!service.empty()) |
| { |
| DBusValue value; |
| getProperty(service, object_path::logSetting, interface::logSetting, |
| "QuiesceOnHwError", value); |
| |
| ret = std::get<bool>(value); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::warning("Failed reading QuiesceOnHwError property from " |
| "interface: {IFACE} exception: {ERROR}", |
| "IFACE", interface::logSetting, "ERROR", e); |
| } |
| |
| return ret; |
| } |
| |
| std::vector<bool> |
| DataInterface::checkDumpStatus(const std::vector<std::string>& type) const |
| { |
| DBusSubTree subtree; |
| std::vector<bool> result(type.size(), false); |
| |
| // Query GetSubTree for the availability of dump interface |
| auto method = _bus.new_method_call(service_name::objectMapper, |
| object_path::objectMapper, |
| interface::objectMapper, "GetSubTree"); |
| method.append(std::string{"/"}, 0, |
| std::vector<std::string>{interface::dumpEntry}); |
| auto reply = _bus.call(method, dbusTimeout); |
| |
| reply.read(subtree); |
| |
| if (subtree.empty()) |
| { |
| return result; |
| } |
| |
| std::vector<bool>::iterator itDumpStatus = result.begin(); |
| uint8_t count = 0; |
| for (const auto& [path, serviceInfo] : subtree) |
| { |
| const auto& service = serviceInfo.begin()->first; |
| // Check for dump type on the object path |
| for (const auto& it : type) |
| { |
| if (path.find(it) != std::string::npos) |
| { |
| DBusValue value, progress; |
| |
| // If dump type status is already available go for next path |
| if (*itDumpStatus) |
| { |
| break; |
| } |
| |
| // Check for valid dump to be available if following |
| // conditions are met for the dump entry path - |
| // Offloaded == false and Status == Completed |
| getProperty(service, path, interface::dumpEntry, "Offloaded", |
| value); |
| getProperty(service, path, interface::dumpProgress, "Status", |
| progress); |
| auto offload = std::get<bool>(value); |
| auto status = std::get<std::string>(progress); |
| if (!offload && (status.find("Completed") != std::string::npos)) |
| { |
| *itDumpStatus = true; |
| count++; |
| if (count >= type.size()) |
| { |
| return result; |
| } |
| break; |
| } |
| } |
| ++itDumpStatus; |
| } |
| itDumpStatus = result.begin(); |
| } |
| |
| return result; |
| } |
| |
| void DataInterface::createGuardRecord(const std::vector<uint8_t>& binPath, |
| const std::string& type, |
| const std::string& logPath) const |
| { |
| try |
| { |
| auto method = _bus.new_method_call( |
| service_name::hwIsolation, object_path::hwIsolation, |
| interface::hwIsolationCreate, "CreateWithEntityPath"); |
| method.append(binPath, type, sdbusplus::message::object_path(logPath)); |
| // Note: hw isolation "CreateWithEntityPath" got dependency on logging |
| // api's. Making d-bus call no reply type to avoid cyclic dependency. |
| // Added minimal timeout to catch initial failures. |
| // Need to revisit this design later to avoid cyclic dependency. |
| constexpr auto hwIsolationTimeout = 100000; // in micro seconds |
| _bus.call_noreply(method, hwIsolationTimeout); |
| } |
| |
| catch (const sdbusplus::exception_t& e) |
| { |
| std::string errName = e.name(); |
| // SD_BUS_ERROR_TIMEOUT error is expected, due to PEL api dependency |
| // mentioned above. Ignoring the error. |
| if (errName != SD_BUS_ERROR_TIMEOUT) |
| { |
| lg2::error("GUARD D-Bus call exception. Path={PATH}, " |
| "interface = {IFACE}, exception = {ERROR}", |
| "PATH", object_path::hwIsolation, "IFACE", |
| interface::hwIsolationCreate, "ERROR", e); |
| } |
| } |
| } |
| |
| void DataInterface::createProgressSRC( |
| const uint64_t& priSRC, const std::vector<uint8_t>& srcStruct) const |
| { |
| DBusValue variant = std::make_tuple(priSRC, srcStruct); |
| |
| auto method = _bus.new_method_call(service_name::bootRawProgress, |
| object_path::bootRawProgress, |
| interface::dbusProperty, "Set"); |
| |
| method.append(interface::bootRawProgress, "Value", variant); |
| |
| _bus.call(method, dbusTimeout); |
| } |
| |
| std::vector<uint32_t> DataInterface::getLogIDWithHwIsolation() const |
| { |
| std::vector<std::string> association = {"xyz.openbmc_project.Association"}; |
| std::string hwErrorLog = "/isolated_hw_errorlog"; |
| std::string errorLog = "/error_log"; |
| DBusPathList paths; |
| std::vector<uint32_t> ids; |
| |
| // Get all latest mapper associations |
| paths = getPaths(association); |
| for (auto& path : paths) |
| { |
| // Look for object path with hardware isolation entry if any |
| size_t pos = path.find(hwErrorLog); |
| if (pos != std::string::npos) |
| { |
| // Get the object path |
| std::string ph = path; |
| ph.erase(pos, hwErrorLog.length()); |
| auto service = getService(ph, interface::hwIsolationEntry); |
| if (!service.empty()) |
| { |
| bool status; |
| DBusValue value; |
| |
| // Read the Resolved property from object path |
| getProperty(service, ph, interface::hwIsolationEntry, |
| "Resolved", value); |
| |
| status = std::get<bool>(value); |
| |
| // If the entry isn't resolved |
| if (!status) |
| { |
| auto assocService = |
| getService(path, interface::association); |
| if (!assocService.empty()) |
| { |
| DBusValue endpoints; |
| |
| // Read Endpoints property |
| getProperty(assocService, path, interface::association, |
| "endpoints", endpoints); |
| |
| auto logPath = |
| std::get<std::vector<std::string>>(endpoints); |
| if (!logPath.empty()) |
| { |
| // Get OpenBMC event log Id |
| uint32_t id = stoi(logPath[0].substr( |
| logPath[0].find_last_of('/') + 1)); |
| ids.push_back(id); |
| } |
| } |
| } |
| } |
| } |
| |
| // Look for object path with error_log entry if any |
| pos = path.find(errorLog); |
| if (pos != std::string::npos) |
| { |
| auto service = getService(path, interface::association); |
| if (!service.empty()) |
| { |
| DBusValue value; |
| |
| // Read Endpoints property |
| getProperty(service, path, interface::association, "endpoints", |
| value); |
| |
| auto logPath = std::get<std::vector<std::string>>(value); |
| if (!logPath.empty()) |
| { |
| // Get OpenBMC event log Id |
| uint32_t id = stoi( |
| logPath[0].substr(logPath[0].find_last_of('/') + 1)); |
| ids.push_back(id); |
| } |
| } |
| } |
| } |
| |
| if (ids.size() > 1) |
| { |
| // remove duplicates to have only unique ids |
| std::sort(ids.begin(), ids.end()); |
| ids.erase(std::unique(ids.begin(), ids.end()), ids.end()); |
| } |
| return ids; |
| } |
| |
| std::vector<uint8_t> DataInterface::getRawProgressSRC(void) const |
| { |
| using RawProgressProperty = std::tuple<uint64_t, std::vector<uint8_t>>; |
| |
| DBusValue value; |
| getProperty(service_name::bootRawProgress, object_path::bootRawProgress, |
| interface::bootRawProgress, "Value", value); |
| |
| const auto& rawProgress = std::get<RawProgressProperty>(value); |
| return std::get<1>(rawProgress); |
| } |
| |
| std::optional<std::vector<uint8_t>> |
| DataInterface::getDIProperty(const std::string& locationCode) const |
| { |
| std::vector<uint8_t> viniDI; |
| |
| try |
| { |
| // Note : The hardcoded value 0 should be changed when comes to |
| // multinode system. |
| auto objectPath = getInventoryFromLocCode(locationCode, 0, true); |
| |
| DBusValue value; |
| getProperty(service_name::inventoryManager, objectPath[0], |
| interface::viniRecordVPD, "DI", value); |
| |
| viniDI = std::get<std::vector<uint8_t>>(value); |
| } |
| catch (const std::exception& e) |
| { |
| lg2::warning( |
| "Failed reading DI property for the location code : {LOC_CODE} from " |
| "interface: {IFACE} exception: {ERROR}", |
| "LOC_CODE", locationCode, "IFACE", interface::viniRecordVPD, |
| "ERROR", e); |
| return std::nullopt; |
| } |
| |
| return viniDI; |
| } |
| |
| std::optional<bool> |
| DataInterfaceBase::isDIMMLocCode(const std::string& locCode) const |
| { |
| if (_locationCache.contains(locCode)) |
| { |
| return _locationCache.at(locCode); |
| } |
| else |
| { |
| return std::nullopt; |
| } |
| } |
| |
| void DataInterfaceBase::addDIMMLocCode(const std::string& locCode, |
| bool isFRUDIMM) |
| { |
| _locationCache.insert({locCode, isFRUDIMM}); |
| } |
| |
| bool DataInterfaceBase::isDIMM(const std::string& locCode) |
| { |
| auto isDIMMType = isDIMMLocCode(locCode); |
| if (isDIMMType.has_value()) |
| { |
| return isDIMMType.value(); |
| } |
| #ifndef PEL_ENABLE_PHAL |
| return false; |
| #else |
| else |
| { |
| // Invoke pHAL API inorder to fetch the FRU Type |
| auto fruType = openpower::phal::pdbg::getFRUType(locCode); |
| bool isDIMMFRU{false}; |
| if (fruType.has_value()) |
| { |
| if (fruType.value() == ENUM_ATTR_TYPE_DIMM) |
| { |
| isDIMMFRU = true; |
| } |
| addDIMMLocCode(locCode, isDIMMFRU); |
| } |
| return isDIMMFRU; |
| } |
| #endif |
| } |
| |
| DBusPathList DataInterface::getAssociatedPaths( |
| const DBusPath& associatedPath, const DBusPath& subtree, int32_t depth, |
| const DBusInterfaceList& interfaces) const |
| { |
| DBusPathList paths; |
| try |
| { |
| auto method = _bus.new_method_call( |
| service_name::objectMapper, object_path::objectMapper, |
| interface::objectMapper, "GetAssociatedSubTreePaths"); |
| method.append(sdbusplus::message::object_path(associatedPath), |
| sdbusplus::message::object_path(subtree), depth, |
| interfaces); |
| |
| auto reply = _bus.call(method, dbusTimeout); |
| reply.read(paths); |
| } |
| catch (const std::exception& e) |
| { |
| std::string ifaces( |
| std::ranges::fold_left_first( |
| interfaces, |
| [](std::string ifaces, const std::string& iface) { |
| return ifaces + ", " + iface; |
| }) |
| .value_or("")); |
| |
| lg2::error("Failed getting associated paths: {ERROR}. " |
| "AssociatedPath: {ASSOIC_PATH} Subtree: {SUBTREE} " |
| "Interfaces: {IFACES}", |
| "ERROR", e, "ASSOIC_PATH", associatedPath, "SUBTREE", |
| subtree, "IFACES", ifaces); |
| } |
| return paths; |
| } |
| |
| void DataInterface::startFruPlugWatch() |
| { |
| // Add a watch on inventory InterfacesAdded and then find all |
| // existing hotpluggable interfaces and add propertiesChanged |
| // watches on them. |
| |
| _invIaMatch = std::make_unique<sdbusplus::bus::match_t>( |
| _bus, match_rules::interfacesAdded(object_path::baseInv), |
| std::bind(&DataInterface::inventoryIfaceAdded, this, |
| std::placeholders::_1)); |
| try |
| { |
| auto paths = getPaths(hotplugInterfaces); |
| |
| _invPresentMatches.clear(); |
| |
| std::for_each(paths.begin(), paths.end(), |
| [this](const auto& path) { addHotplugWatch(path); }); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| lg2::warning("Failed getting FRU paths to watch: {ERROR}", "ERROR", e); |
| } |
| } |
| |
| void DataInterface::addHotplugWatch(const std::string& path) |
| { |
| if (!_invPresentMatches.contains(path)) |
| { |
| _invPresentMatches.emplace( |
| path, |
| std::make_unique<sdbusplus::bus::match_t>( |
| _bus, match_rules::propertiesChanged(path, interface::invItem), |
| std::bind(&DataInterface::presenceChanged, this, |
| std::placeholders::_1))); |
| } |
| } |
| |
| void DataInterface::inventoryIfaceAdded(sdbusplus::message_t& msg) |
| { |
| sdbusplus::message::object_path path; |
| DBusInterfaceMap interfaces; |
| |
| msg.read(path, interfaces); |
| |
| // Check if any of the new interfaces are for hot pluggable FRUs. |
| if (std::find_if(interfaces.begin(), interfaces.end(), |
| [](const auto& interfacePair) { |
| return std::find(hotplugInterfaces.begin(), |
| hotplugInterfaces.end(), |
| interfacePair.first) != |
| hotplugInterfaces.end(); |
| }) == interfaces.end()) |
| { |
| return; |
| } |
| |
| addHotplugWatch(path.str); |
| |
| // If an Inventory.Item interface was also added, check presence now. |
| |
| // Notes: |
| // * This assumes the Inv.Item and Inv.Fan/PS are added together which |
| // is currently the case. |
| // * If the code ever switches to something without a Present |
| // property, then the IA signal itself would probably indicate presence. |
| |
| auto itemIt = interfaces.find(interface::invItem); |
| if (itemIt != interfaces.end()) |
| { |
| notifyPresenceSubsribers(path.str, itemIt->second); |
| } |
| } |
| |
| void DataInterface::presenceChanged(sdbusplus::message_t& msg) |
| { |
| DBusInterface interface; |
| DBusPropertyMap properties; |
| |
| msg.read(interface, properties); |
| if (interface != interface::invItem) |
| { |
| return; |
| } |
| |
| std::string path = msg.get_path(); |
| notifyPresenceSubsribers(path, properties); |
| } |
| |
| void DataInterface::notifyPresenceSubsribers(const std::string& path, |
| const DBusPropertyMap& properties) |
| { |
| auto prop = properties.find("Present"); |
| if ((prop == properties.end()) || (!std::get<bool>(prop->second))) |
| { |
| return; |
| } |
| |
| std::string locCode; |
| |
| try |
| { |
| auto service = getService(path, interface::locCode); |
| |
| // If the hotplugged FRU is hosted by PLDM, then it is |
| // in an IO expansion drawer and we don't care about it. |
| if (service == service_name::pldm) |
| { |
| return; |
| } |
| |
| locCode = getLocationCode(path); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| lg2::debug("Could not get location code for {PATH}: {ERROR}", "PATH", |
| path, "ERROR", e); |
| return; |
| } |
| |
| lg2::debug("Detected FRU {PATH} ({LOC}) present ", "PATH", path, "LOC", |
| locCode); |
| |
| // Tell the subscribers. |
| setFruPresent(locCode); |
| } |
| |
| bool DataInterface::isPHALDevTreeExist() const |
| { |
| try |
| { |
| if (std::filesystem::exists(PDBG_DTB_PATH)) |
| { |
| return true; |
| } |
| } |
| catch (const std::exception& e) |
| { |
| lg2::error("Failed to check device tree {PHAL_DEVTREE_PATH} existence, " |
| "{ERROR}", |
| "PHAL_DEVTREE_PATH", PDBG_DTB_PATH, "ERROR", e); |
| } |
| return false; |
| } |
| |
| #ifdef PEL_ENABLE_PHAL |
| void DataInterface::initPHAL() |
| { |
| if (setenv("PDBG_DTB", PDBG_DTB_PATH, 1)) |
| { |
| // Log message and continue, |
| // This is to help continue creating PEL in raw format. |
| lg2::error("Failed to set PDBG_DTB: ({ERRNO})", "ERRNO", |
| strerror(errno)); |
| } |
| |
| if (!pdbg_targets_init(NULL)) |
| { |
| lg2::error("pdbg_targets_init failed"); |
| return; |
| } |
| |
| if (libekb_init()) |
| { |
| lg2::error("libekb_init failed, skipping ffdc processing"); |
| return; |
| } |
| } |
| #endif |
| |
| void DataInterface::subscribeToSystemdSignals() |
| { |
| try |
| { |
| auto method = |
| _bus.new_method_call(service_name::systemd, object_path::systemd, |
| interface::systemdMgr, "Subscribe"); |
| _systemdSlot = method.call_async([this](sdbusplus::message_t&& msg) { |
| // Initializing with nullptr to indicate that it is not subscribed |
| // to any signal. |
| this->_systemdSlot = sdbusplus::slot_t(nullptr); |
| if (msg.is_method_error()) |
| { |
| auto* error = msg.get_error(); |
| lg2::error("Failed to subscribe JobRemoved systemd signal, " |
| "errorName: {ERR_NAME}, errorMsg: {ERR_MSG} ", |
| "ERR_NAME", error->name, "ERR_MSG", error->message); |
| return; |
| } |
| |
| namespace sdbusRule = sdbusplus::bus::match::rules; |
| this->_systemdMatch = |
| std::make_unique<decltype(this->_systemdMatch)::element_type>( |
| this->_bus, |
| sdbusRule::type::signal() + |
| sdbusRule::member("JobRemoved") + |
| sdbusRule::path(object_path::systemd) + |
| sdbusRule::interface(interface::systemdMgr), |
| [this](sdbusplus::message_t& msg) { |
| uint32_t jobID; |
| sdbusplus::message::object_path jobObjPath; |
| std::string jobUnitName, jobUnitResult; |
| |
| msg.read(jobID, jobObjPath, jobUnitName, jobUnitResult); |
| if ((jobUnitName == |
| "openpower-update-bios-attr-table.service") && |
| (jobUnitResult == "done")) |
| { |
| #ifdef PEL_ENABLE_PHAL |
| this->initPHAL(); |
| #endif |
| // Invoke unsubscribe method to stop monitoring for |
| // JobRemoved signals. |
| this->unsubscribeFromSystemdSignals(); |
| } |
| }); |
| }); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| lg2::error( |
| "Exception occured while handling JobRemoved systemd signal, " |
| "exception: {ERROR}", |
| "ERROR", e); |
| } |
| } |
| |
| void DataInterface::unsubscribeFromSystemdSignals() |
| { |
| try |
| { |
| auto method = |
| _bus.new_method_call(service_name::systemd, object_path::systemd, |
| interface::systemdMgr, "Unsubscribe"); |
| _systemdSlot = method.call_async([this](sdbusplus::message_t&& msg) { |
| // Unsubscribing the _systemdSlot from the subscribed signal |
| this->_systemdSlot = sdbusplus::slot_t(nullptr); |
| if (msg.is_method_error()) |
| { |
| auto* error = msg.get_error(); |
| lg2::error( |
| "Failed to unsubscribe from JobRemoved systemd signal, " |
| "errorName: {ERR_NAME}, errorMsg: {ERR_MSG} ", |
| "ERR_NAME", error->name, "ERR_MSG", error->message); |
| return; |
| } |
| // Reset _systemdMatch to avoid reception of further JobRemoved |
| // signals |
| this->_systemdMatch.reset(); |
| }); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| lg2::error( |
| "Exception occured while unsubscribing from JobRemoved systemd signal, " |
| "exception: {ERROR}", |
| "ERROR", e); |
| } |
| } |
| |
| } // namespace pels |
| } // namespace openpower |