|  | /** | 
|  | * 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 "config.h" | 
|  |  | 
|  | #include "data_interface.hpp" | 
|  |  | 
|  | #include "util.hpp" | 
|  |  | 
|  | #include <fstream> | 
|  | #include <xyz/openbmc_project/State/OperatingSystem/Status/server.hpp> | 
|  |  | 
|  | 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"; | 
|  | } // namespace service_name | 
|  |  | 
|  | namespace object_path | 
|  | { | 
|  | constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper"; | 
|  | constexpr auto systemInv = "/xyz/openbmc_project/inventory/system"; | 
|  | constexpr auto chassisInv = "/xyz/openbmc_project/inventory/system/chassis"; | 
|  | 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 pldm = "/xyz/openbmc_project/pldm"; | 
|  | constexpr auto enableHostPELs = | 
|  | "/xyz/openbmc_project/logging/send_event_logs_to_host"; | 
|  | constexpr auto vpdManager = "/com/ibm/VPD/Manager"; | 
|  | } // 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 osStatus = "xyz.openbmc_project.State.OperatingSystem.Status"; | 
|  | constexpr auto pldmRequester = "xyz.openbmc_project.PLDM.Requester"; | 
|  | 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 invMotherboard = | 
|  | "xyz.openbmc_project.Inventory.Item.Board.Motherboard"; | 
|  | constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI"; | 
|  | constexpr auto locCode = "com.ibm.ipzvpd.Location"; | 
|  | constexpr auto compatible = | 
|  | "xyz.openbmc_project.Configuration.IBMCompatibleSystem"; | 
|  | constexpr auto vpdManager = "com.ibm.VPD.Manager"; | 
|  | constexpr auto association = "xyz.openbmc_project.Association"; | 
|  | constexpr auto ledGroup = "xyz.openbmc_project.Led.Group"; | 
|  | } // namespace interface | 
|  |  | 
|  | using namespace sdbusplus::xyz::openbmc_project::State::OperatingSystem::server; | 
|  | using sdbusplus::exception::SdBusError; | 
|  |  | 
|  | DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus) | 
|  | { | 
|  | readBMCFWVersion(); | 
|  | readServerFWVersion(); | 
|  | readBMCFWVersionID(); | 
|  | readMotherboardCCIN(); | 
|  |  | 
|  | // Watch both the Model and SN properties on the system's Asset iface | 
|  | _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>( | 
|  | bus, object_path::systemInv, interface::invAsset, *this, | 
|  | [this](const auto& properties) { | 
|  | auto model = properties.find("Model"); | 
|  | if (model != properties.end()) | 
|  | { | 
|  | this->_machineTypeModel = std::get<std::string>(model->second); | 
|  | } | 
|  |  | 
|  | auto sn = properties.find("SerialNumber"); | 
|  | if (sn != properties.end()) | 
|  | { | 
|  | this->_machineSerialNumber = std::get<std::string>(sn->second); | 
|  | } | 
|  | })); | 
|  |  | 
|  | // Watch the OperatingSystemState property | 
|  | _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( | 
|  | bus, object_path::hostState, interface::osStatus, | 
|  | "OperatingSystemState", *this, [this](const auto& value) { | 
|  | auto status = | 
|  | Status::convertOSStatusFromString(std::get<std::string>(value)); | 
|  |  | 
|  | if ((status == Status::OSStatus::BootComplete) || | 
|  | (status == Status::OSStatus::Standby)) | 
|  | { | 
|  | 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) { | 
|  | 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) { | 
|  | this->_bmcState = std::get<std::string>(value); | 
|  | })); | 
|  |  | 
|  | // 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); | 
|  | })); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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() | 
|  | { | 
|  | // Not available yet | 
|  | } | 
|  |  | 
|  | void DataInterface::readBMCFWVersionID() | 
|  | { | 
|  | _bmcFWVersionID = | 
|  | phosphor::logging::util::getOSReleaseValue("VERSION_ID").value_or(""); | 
|  | } | 
|  |  | 
|  | void DataInterface::readMotherboardCCIN() | 
|  | { | 
|  | try | 
|  | { | 
|  | // First, try to find the motherboard | 
|  | auto motherboards = getPaths({interface::invMotherboard}); | 
|  | if (motherboards.empty()) | 
|  | { | 
|  | throw std::runtime_error("No motherboards yet"); | 
|  | } | 
|  |  | 
|  | // Found it, so now get the CCIN | 
|  | _properties.emplace_back( | 
|  | std::make_unique<PropertyWatcher<DataInterface>>( | 
|  | _bus, motherboards.front(), interface::viniRecordVPD, "CC", | 
|  | *this, | 
|  | [this](const auto& ccin) { this->setMotherboardCCIN(ccin); })); | 
|  | } | 
|  | catch (const std::exception& e) | 
|  | { | 
|  | // No motherboard in the inventory yet - watch for it | 
|  | _inventoryIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>( | 
|  | _bus, match_rules::interfacesAdded(object_path::baseInv), | 
|  | std::bind(std::mem_fn(&DataInterface::motherboardIfaceAdded), this, | 
|  | std::placeholders::_1)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DataInterface::motherboardIfaceAdded(sdbusplus::message::message& msg) | 
|  | { | 
|  | sdbusplus::message::object_path path; | 
|  | DBusInterfaceMap interfaces; | 
|  |  | 
|  | msg.read(path, interfaces); | 
|  |  | 
|  | // This is watching the whole inventory, so check if it's what we want | 
|  | if (interfaces.find(interface::invMotherboard) == interfaces.end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Done watching for any new inventory interfaces | 
|  | _inventoryIfacesAddedMatch.reset(); | 
|  |  | 
|  | // Watch the motherboard CCIN, using the service from this signal | 
|  | // for the initial property read. | 
|  | _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>( | 
|  | _bus, path, interface::viniRecordVPD, "CC", msg.get_sender(), *this, | 
|  | [this](const auto& ccin) { this->setMotherboardCCIN(ccin); })); | 
|  | } | 
|  |  | 
|  | 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 | 
|  | { | 
|  | auto method = | 
|  | _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, | 
|  | interface::vpdManager, "GetExpandedLocationCode"); | 
|  |  | 
|  | method.append(addLocationCodePrefix(locationCode), | 
|  | static_cast<uint16_t>(0)); | 
|  |  | 
|  | auto reply = _bus.call(method); | 
|  |  | 
|  | std::string expandedLocationCode; | 
|  | reply.read(expandedLocationCode); | 
|  |  | 
|  | return expandedLocationCode; | 
|  | } | 
|  |  | 
|  | std::string | 
|  | DataInterface::getInventoryFromLocCode(const std::string& locationCode, | 
|  | uint16_t node, bool expanded) const | 
|  | { | 
|  | std::string methodName = expanded ? "GetFRUsByExpandedLocationCode" | 
|  | : "GetFRUsByUnexpandedLocationCode"; | 
|  |  | 
|  | auto method = | 
|  | _bus.new_method_call(service_name::vpdManager, object_path::vpdManager, | 
|  | interface::vpdManager, methodName.c_str()); | 
|  |  | 
|  | if (expanded) | 
|  | { | 
|  | method.append(locationCode); | 
|  | } | 
|  | else | 
|  | { | 
|  | method.append(addLocationCodePrefix(locationCode), node); | 
|  | } | 
|  |  | 
|  | auto reply = _bus.call(method); | 
|  |  | 
|  | std::vector<sdbusplus::message::object_path> entries; | 
|  | reply.read(entries); | 
|  |  | 
|  | // Get the shortest entry from the paths received, as this | 
|  | // would be the path furthest up the inventory hierarchy so | 
|  | // would be the parent FRU.  There is guaranteed to at least | 
|  | // be one entry if the call didn't fail. | 
|  | std::string shortest{entries[0]}; | 
|  |  | 
|  | std::for_each(entries.begin(), entries.end(), | 
|  | [&shortest](const auto& path) { | 
|  | if (path.str.size() < shortest.size()) | 
|  | { | 
|  | shortest = path; | 
|  | } | 
|  | }); | 
|  |  | 
|  | return shortest; | 
|  | } | 
|  |  | 
|  | std::string | 
|  | DataInterface::getFaultLEDGroup(const std::string& inventoryPath) const | 
|  | { | 
|  | auto associationPath = inventoryPath + "/" + "fault_led_group"; | 
|  | auto service = getService(associationPath, interface::association); | 
|  |  | 
|  | DBusValue endpoints; | 
|  | getProperty(service, associationPath, interface::association, "endpoints", | 
|  | endpoints); | 
|  | auto paths = std::get<std::vector<std::string>>(endpoints); | 
|  | if (paths.empty()) | 
|  | { | 
|  | throw std::runtime_error("Association endpoints property empty"); | 
|  | } | 
|  |  | 
|  | return paths[0]; | 
|  | } | 
|  |  | 
|  | 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); | 
|  | } | 
|  |  | 
|  | 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); | 
|  |  | 
|  | reply.read(subtree); | 
|  | if (subtree.empty()) | 
|  | { | 
|  | throw std::runtime_error("Compatible interface not on D-Bus"); | 
|  | } | 
|  |  | 
|  | const auto& object = *(subtree.begin()); | 
|  | const auto& path = object.first; | 
|  | const auto& service = object.second.begin()->first; | 
|  |  | 
|  | getProperty(service, path, interface::compatible, "Names", names); | 
|  |  | 
|  | return std::get<std::vector<std::string>>(names); | 
|  | } | 
|  |  | 
|  | } // namespace pels | 
|  | } // namespace openpower |