| #pragma once |
| |
| #include "constants.hpp" |
| #include "exceptions.hpp" |
| #include "logger.hpp" |
| #include "types.hpp" |
| |
| #include <chrono> |
| |
| namespace vpd |
| { |
| /** |
| * @brief The namespace defines utlity methods for generic D-Bus operations. |
| */ |
| namespace dbusUtility |
| { |
| |
| /** |
| * @brief An API to get Map of service and interfaces for an object path. |
| * |
| * The API returns a Map of service name and interfaces for a given pair of |
| * object path and interface list. It can be used to determine service name |
| * which implemets a particular object path and interface. |
| * |
| * Note: It will be caller's responsibility to check for empty map returned and |
| * generate appropriate error. |
| * |
| * @param [in] objectPath - Object path under the service. |
| * @param [in] interfaces - Array of interface(s). |
| * @return - A Map of service name to object to interface(s), if success. |
| * If failed, empty map. |
| */ |
| inline types::MapperGetObject getObjectMap(const std::string& objectPath, |
| std::span<const char*> interfaces) |
| { |
| types::MapperGetObject getObjectMap; |
| |
| // interface list is optional argument, hence no check required. |
| if (objectPath.empty()) |
| { |
| logging::logMessage("Path value is empty, invalid call to GetObject"); |
| return getObjectMap; |
| } |
| |
| try |
| { |
| auto bus = sdbusplus::bus::new_default(); |
| auto method = bus.new_method_call( |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetObject"); |
| |
| method.append(objectPath, interfaces); |
| auto result = bus.call(method); |
| result.read(getObjectMap); |
| } |
| catch (const sdbusplus::exception::SdBusError& e) |
| { |
| // logging::logMessage(e.what()); |
| return getObjectMap; |
| } |
| |
| return getObjectMap; |
| } |
| |
| /** |
| * @brief An API to get property map for an interface. |
| * |
| * This API returns a map of property and its value with respect to a particular |
| * interface. |
| * |
| * Note: It will be caller's responsibility to check for empty map returned and |
| * generate appropriate error. |
| * |
| * @param[in] i_service - Service name. |
| * @param[in] i_objectPath - object path. |
| * @param[in] i_interface - Interface, for the properties to be listed. |
| * |
| * @return - A map of property and value of an interface, if success. |
| * if failed, empty map. |
| */ |
| inline types::PropertyMap getPropertyMap(const std::string& i_service, |
| const std::string& i_objectPath, |
| const std::string& i_interface) |
| { |
| types::PropertyMap l_propertyValueMap; |
| if (i_service.empty() || i_objectPath.empty() || i_interface.empty()) |
| { |
| logging::logMessage("Invalid parameters to get property map"); |
| return l_propertyValueMap; |
| } |
| |
| try |
| { |
| auto l_bus = sdbusplus::bus::new_default(); |
| auto l_method = |
| l_bus.new_method_call(i_service.c_str(), i_objectPath.c_str(), |
| "org.freedesktop.DBus.Properties", "GetAll"); |
| l_method.append(i_interface); |
| auto l_result = l_bus.call(l_method); |
| l_result.read(l_propertyValueMap); |
| } |
| catch (const sdbusplus::exception::SdBusError& l_ex) |
| { |
| logging::logMessage(l_ex.what()); |
| } |
| |
| return l_propertyValueMap; |
| } |
| |
| /** |
| * @brief API to get object subtree from D-bus. |
| * |
| * The API returns the map of object, services and interfaces in the |
| * subtree that implement a certain interface. If no interfaces are provided |
| * then all the objects, services and interfaces under the subtree will |
| * be returned. |
| * |
| * Note: Depth can be 0 and interfaces can be null. |
| * It will be caller's responsibility to check for empty vector returned |
| * and generate appropriate error. |
| * |
| * @param[in] i_objectPath - Path to search for an interface. |
| * @param[in] i_depth - Maximum depth of the tree to search. |
| * @param[in] i_interfaces - List of interfaces to search. |
| * |
| * @return - A map of object and its related services and interfaces, if |
| * success. If failed, empty map. |
| */ |
| |
| inline types::MapperGetSubTree getObjectSubTree( |
| const std::string& i_objectPath, const int& i_depth, |
| const std::vector<std::string>& i_interfaces) |
| { |
| types::MapperGetSubTree l_subTreeMap; |
| |
| if (i_objectPath.empty()) |
| { |
| logging::logMessage("Object path is empty."); |
| return l_subTreeMap; |
| } |
| |
| try |
| { |
| auto l_bus = sdbusplus::bus::new_default(); |
| auto l_method = l_bus.new_method_call( |
| constants::objectMapperService, constants::objectMapperPath, |
| constants::objectMapperInf, "GetSubTree"); |
| l_method.append(i_objectPath, i_depth, i_interfaces); |
| auto l_result = l_bus.call(l_method); |
| l_result.read(l_subTreeMap); |
| } |
| catch (const sdbusplus::exception::SdBusError& l_ex) |
| { |
| logging::logMessage(l_ex.what()); |
| } |
| |
| return l_subTreeMap; |
| } |
| |
| /** |
| * @brief An API to read property from Dbus. |
| * |
| * The caller of the API needs to validate the validatity and correctness of the |
| * type and value of data returned. The API will just fetch and retun the data |
| * without any data validation. |
| * |
| * Note: It will be caller's responsibility to check for empty value returned |
| * and generate appropriate error if required. |
| * |
| * @param [in] serviceName - Name of the Dbus service. |
| * @param [in] objectPath - Object path under the service. |
| * @param [in] interface - Interface under which property exist. |
| * @param [in] property - Property whose value is to be read. |
| * @return - Value read from Dbus, if success. |
| * If failed, empty variant. |
| */ |
| inline types::DbusVariantType readDbusProperty( |
| const std::string& serviceName, const std::string& objectPath, |
| const std::string& interface, const std::string& property) |
| { |
| types::DbusVariantType propertyValue; |
| |
| // Mandatory fields to make a read dbus call. |
| if (serviceName.empty() || objectPath.empty() || interface.empty() || |
| property.empty()) |
| { |
| logging::logMessage( |
| "One of the parameter to make Dbus read call is empty."); |
| return propertyValue; |
| } |
| |
| try |
| { |
| auto bus = sdbusplus::bus::new_default(); |
| auto method = |
| bus.new_method_call(serviceName.c_str(), objectPath.c_str(), |
| "org.freedesktop.DBus.Properties", "Get"); |
| method.append(interface, property); |
| |
| auto result = bus.call(method); |
| result.read(propertyValue); |
| } |
| catch (const sdbusplus::exception::SdBusError& e) |
| { |
| return propertyValue; |
| } |
| return propertyValue; |
| } |
| |
| /** |
| * @brief An API to write property on Dbus. |
| * |
| * The caller of this API needs to handle exception thrown by this method to |
| * identify any write failure. The API in no other way indicate write failure |
| * to the caller. |
| * |
| * @param [in] serviceName - Name of the Dbus service. |
| * @param [in] objectPath - Object path under the service. |
| * @param [in] interface - Interface under which property exist. |
| * @param [in] property - Property whose value is to be written. |
| * @param [in] propertyValue - The value to be written. |
| * @return True if write on DBus is success, false otherwise. |
| */ |
| inline bool writeDbusProperty( |
| const std::string& serviceName, const std::string& objectPath, |
| const std::string& interface, const std::string& property, |
| const types::DbusVariantType& propertyValue) |
| { |
| try |
| { |
| // Mandatory fields to make a write dbus call. |
| if (serviceName.empty() || objectPath.empty() || interface.empty() || |
| property.empty()) |
| { |
| throw std::runtime_error("Dbus write failed, Parameter empty"); |
| } |
| |
| auto bus = sdbusplus::bus::new_default(); |
| auto method = |
| bus.new_method_call(serviceName.c_str(), objectPath.c_str(), |
| "org.freedesktop.DBus.Properties", "Set"); |
| method.append(interface, property, propertyValue); |
| bus.call(method); |
| |
| return true; |
| } |
| catch (const std::exception& l_ex) |
| { |
| logging::logMessage( |
| "DBus write failed, error: " + std::string(l_ex.what())); |
| return false; |
| } |
| } |
| |
| /** |
| * @brief API to publish data on PIM |
| * |
| * The API calls notify on PIM object to publlish VPD. |
| * |
| * @param[in] objectMap - Object, its interface and data. |
| * @return bool - Status of call to PIM notify. |
| */ |
| inline bool callPIM(types::ObjectMap&& objectMap) |
| { |
| try |
| { |
| for (const auto& l_objectKeyValue : objectMap) |
| { |
| auto l_nodeHandle = objectMap.extract(l_objectKeyValue.first); |
| |
| if (l_nodeHandle.key().str.find(constants::pimPath, 0) != |
| std::string::npos) |
| { |
| l_nodeHandle.key() = l_nodeHandle.key().str.replace( |
| 0, std::strlen(constants::pimPath), ""); |
| objectMap.insert(std::move(l_nodeHandle)); |
| } |
| } |
| |
| auto bus = sdbusplus::bus::new_default(); |
| auto pimMsg = |
| bus.new_method_call(constants::pimServiceName, constants::pimPath, |
| constants::pimIntf, "Notify"); |
| pimMsg.append(std::move(objectMap)); |
| bus.call(pimMsg); |
| } |
| catch (const sdbusplus::exception::SdBusError& e) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * @brief API to check if a D-Bus service is running or not. |
| * |
| * Any failure in calling the method "NameHasOwner" implies that the service is |
| * not in a running state. Hence the API returns false in case of any exception |
| * as well. |
| * |
| * @param[in] i_serviceName - D-Bus service name whose status is to be checked. |
| * @return bool - True if the service is running, false otherwise. |
| */ |
| inline bool isServiceRunning(const std::string& i_serviceName) |
| { |
| bool l_retVal = false; |
| |
| try |
| { |
| auto l_bus = sdbusplus::bus::new_default(); |
| auto l_method = l_bus.new_method_call( |
| "org.freedesktop.DBus", "/org/freedesktop/DBus", |
| "org.freedesktop.DBus", "NameHasOwner"); |
| l_method.append(i_serviceName); |
| |
| l_bus.call(l_method).read(l_retVal); |
| } |
| catch (const sdbusplus::exception::SdBusError& l_ex) |
| { |
| logging::logMessage( |
| "Call to check service status failed with exception: " + |
| std::string(l_ex.what())); |
| } |
| |
| return l_retVal; |
| } |
| |
| /** |
| * @brief API to call "GetAttribute" method uner BIOS manager. |
| * |
| * The API reads the given attribuute from BIOS and returns a tuple of both |
| * current as well as pending value for that attribute. |
| * The API return only the current attribute value if found. |
| * API returns an empty variant of type BiosAttributeCurrentValue in case of any |
| * error. |
| * |
| * @param[in] i_attributeName - Attribute to be read. |
| * @return Tuple of PLDM attribute Type, current attribute value and pending |
| * attribute value. |
| */ |
| inline types::BiosAttributeCurrentValue biosGetAttributeMethodCall( |
| const std::string& i_attributeName) |
| { |
| auto l_bus = sdbusplus::bus::new_default(); |
| auto l_method = l_bus.new_method_call( |
| constants::biosConfigMgrService, constants::biosConfigMgrObjPath, |
| constants::biosConfigMgrInterface, "GetAttribute"); |
| l_method.append(i_attributeName); |
| |
| types::BiosGetAttrRetType l_attributeVal; |
| try |
| { |
| auto l_result = l_bus.call(l_method); |
| l_result.read(std::get<0>(l_attributeVal), std::get<1>(l_attributeVal), |
| std::get<2>(l_attributeVal)); |
| } |
| catch (const sdbusplus::exception::SdBusError& l_ex) |
| { |
| logging::logMessage( |
| "Failed to read BIOS Attribute: " + i_attributeName + |
| " due to error " + std::string(l_ex.what())); |
| |
| // TODO: Log an informational PEL here. |
| } |
| |
| return std::get<1>(l_attributeVal); |
| } |
| |
| /** |
| * @brief API to check if Chassis is powered on. |
| * |
| * This API queries Phosphor Chassis State Manager to know whether |
| * Chassis is powered on. |
| * |
| * @return true if chassis is powered on, false otherwise |
| */ |
| inline bool isChassisPowerOn() |
| { |
| auto powerState = dbusUtility::readDbusProperty( |
| "xyz.openbmc_project.State.Chassis", |
| "/xyz/openbmc_project/state/chassis0", |
| "xyz.openbmc_project.State.Chassis", "CurrentPowerState"); |
| |
| if (auto curPowerState = std::get_if<std::string>(&powerState)) |
| { |
| if ("xyz.openbmc_project.State.Chassis.PowerState.On" == *curPowerState) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| /* |
| TODO: Add PEL. |
| Callout: Firmware callout |
| Type: Informational |
| Description: Chassis state can't be determined, defaulting to chassis |
| off. : e.what() |
| */ |
| return false; |
| } |
| |
| /** |
| * @brief API to check if host is in running state. |
| * |
| * This API reads the current host state from D-bus and returns true if the host |
| * is running. |
| * |
| * @return true if host is in running state. false otherwise. |
| */ |
| inline bool isHostRunning() |
| { |
| const auto l_hostState = dbusUtility::readDbusProperty( |
| constants::hostService, constants::hostObjectPath, |
| constants::hostInterface, "CurrentHostState"); |
| |
| if (const auto l_currHostState = std::get_if<std::string>(&l_hostState)) |
| { |
| if (*l_currHostState == constants::hostRunningState) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @brief API to check if BMC is in ready state. |
| * |
| * This API reads the current state of BMC from D-bus and returns true if BMC is |
| * in ready state. |
| * |
| * @return true if BMC is ready, false otherwise. |
| */ |
| inline bool isBMCReady() |
| { |
| const auto l_bmcState = dbusUtility::readDbusProperty( |
| constants::bmcStateService, constants::bmcZeroStateObject, |
| constants::bmcStateInterface, constants::currentBMCStateProperty); |
| |
| if (const auto l_currBMCState = std::get_if<std::string>(&l_bmcState)) |
| { |
| if (*l_currBMCState == constants::bmcReadyState) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * @brief An API to enable BMC reboot guard |
| * |
| * This API does a D-Bus method call to enable BMC reboot guard. |
| * |
| * @return On success, returns 0, otherwise returns -1. |
| */ |
| inline int EnableRebootGuard() noexcept |
| { |
| int l_rc{constants::FAILURE}; |
| try |
| { |
| auto l_bus = sdbusplus::bus::new_default(); |
| auto l_method = l_bus.new_method_call( |
| constants::systemdService, constants::systemdObjectPath, |
| constants::systemdManagerInterface, "StartUnit"); |
| l_method.append("reboot-guard-enable.service", "replace"); |
| l_bus.call_noreply(l_method); |
| l_rc = constants::SUCCESS; |
| } |
| catch (const sdbusplus::exception::SdBusError& l_ex) |
| { |
| std::string l_errMsg = |
| "D-Bus call to enable BMC reboot guard failed for reason: "; |
| l_errMsg += l_ex.what(); |
| |
| logging::logMessage(l_errMsg); |
| } |
| return l_rc; |
| } |
| |
| /** |
| * @brief An API to disable BMC reboot guard |
| * |
| * This API disables BMC reboot guard. This API has an inbuilt re-try mechanism. |
| * If Disable Reboot Guard fails, this API attempts to Disable Reboot Guard for |
| * 3 more times at an interval of 333ms. |
| * |
| * @return On success, returns 0, otherwise returns -1. |
| */ |
| inline int DisableRebootGuard() noexcept |
| { |
| int l_rc{constants::FAILURE}; |
| |
| // A lambda which executes the DBus call to disable BMC reboot guard. |
| auto l_executeDisableRebootGuard = []() -> int { |
| int l_dBusCallRc{constants::FAILURE}; |
| try |
| { |
| auto l_bus = sdbusplus::bus::new_default(); |
| auto l_method = l_bus.new_method_call( |
| constants::systemdService, constants::systemdObjectPath, |
| constants::systemdManagerInterface, "StartUnit"); |
| l_method.append("reboot-guard-disable.service", "replace"); |
| l_bus.call_noreply(l_method); |
| l_dBusCallRc = constants::SUCCESS; |
| } |
| catch (const sdbusplus::exception::SdBusError& l_ex) |
| {} |
| return l_dBusCallRc; |
| }; |
| |
| if (constants::FAILURE == l_executeDisableRebootGuard()) |
| { |
| std::function<void()> l_retryDisableRebootGuard; |
| |
| // A lambda which tries to disable BMC reboot guard for 3 times at an |
| // interval of 333 ms. |
| l_retryDisableRebootGuard = [&]() { |
| constexpr int MAX_RETRIES{3}; |
| static int l_numRetries{0}; |
| |
| if (l_numRetries < MAX_RETRIES) |
| { |
| l_numRetries++; |
| if (constants::FAILURE == l_executeDisableRebootGuard()) |
| { |
| // sleep for 333ms before next retry. This is just a random |
| // value so that 3 re-tries * 333ms takes ~1 second in the |
| // worst case. |
| const std::chrono::milliseconds l_sleepTime{333}; |
| std::this_thread::sleep_for(l_sleepTime); |
| l_retryDisableRebootGuard(); |
| } |
| else |
| { |
| l_numRetries = 0; |
| l_rc = constants::SUCCESS; |
| } |
| } |
| else |
| { |
| // Failed to Disable Reboot Guard even after 3 retries. |
| logging::logMessage("Failed to Disable Reboot Guard after " + |
| std::to_string(MAX_RETRIES) + " re-tries"); |
| l_numRetries = 0; |
| } |
| }; |
| |
| l_retryDisableRebootGuard(); |
| } |
| else |
| { |
| l_rc = constants::SUCCESS; |
| } |
| return l_rc; |
| } |
| |
| /** |
| * @brief API to notify FRU VPD Collection status. |
| * |
| * This API uses PIM's Notify method to update the given FRU VPD collection |
| * status on D-bus. |
| * |
| * @param[in] i_inventoryPath - D-bus inventory path |
| * @param[in] i_fruCollectionStatus - FRU VPD collection status. |
| * |
| * @return true if update succeeds, false otherwise. |
| */ |
| inline bool notifyFRUCollectionStatus(const std::string& i_inventoryPath, |
| const std::string& i_fruCollectionStatus) |
| { |
| types::ObjectMap l_objectMap; |
| types::InterfaceMap l_interfaceMap; |
| types::PropertyMap l_propertyMap; |
| |
| l_propertyMap.emplace("CollectionStatus", i_fruCollectionStatus); |
| l_interfaceMap.emplace(constants::vpdCollectionInterface, l_propertyMap); |
| l_objectMap.emplace(i_inventoryPath, l_interfaceMap); |
| |
| if (!dbusUtility::callPIM(std::move(l_objectMap))) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @brief API to read IM keyword from Dbus. |
| * |
| * @return IM value read from Dbus, Empty in case of any error. |
| */ |
| inline types::BinaryVector getImFromDbus() |
| { |
| const auto& l_retValue = dbusUtility::readDbusProperty( |
| constants::pimServiceName, constants::systemVpdInvPath, |
| constants::vsbpInf, constants::kwdIM); |
| |
| auto l_imValue = std::get_if<types::BinaryVector>(&l_retValue); |
| if (!l_imValue || (*l_imValue).size() != constants::VALUE_4) |
| { |
| return types::BinaryVector{}; |
| } |
| |
| return *l_imValue; |
| } |
| |
| /** |
| * @brief API to return prefix of functional firmware image. |
| * |
| * Every functional image belongs to a series which is denoted by the first two |
| * characters of the image name. The API extracts that and returns it to the |
| * caller. |
| * |
| * @return Two character string, empty string in case of any error. |
| */ |
| inline std::string getImagePrefix() |
| { |
| try |
| { |
| types::DbusVariantType l_retVal = readDbusProperty( |
| constants::objectMapperService, constants::functionalImageObjPath, |
| constants::associationInterface, "endpoints"); |
| |
| auto l_listOfFunctionalPath = |
| std::get_if<std::vector<std::string>>(&l_retVal); |
| |
| if (!l_listOfFunctionalPath || (*l_listOfFunctionalPath).empty()) |
| { |
| throw DbusException("failed to get functional image path."); |
| } |
| |
| for (const auto& l_imagePath : *l_listOfFunctionalPath) |
| { |
| types::DbusVariantType l_retValPriority = |
| readDbusProperty(constants::imageUpdateService, l_imagePath, |
| constants::imagePrirotyInf, "Priority"); |
| |
| auto l_imagePriority = std::get_if<uint8_t>(&l_retValPriority); |
| if (!l_imagePriority) |
| { |
| throw DbusException( |
| "failed to read functional image priority for path [" + |
| l_imagePath + "]"); |
| } |
| |
| // only interested in running image. |
| if (*l_imagePriority != constants::VALUE_0) |
| { |
| continue; |
| } |
| |
| types::DbusVariantType l_retExVer = readDbusProperty( |
| constants::imageUpdateService, l_imagePath, |
| constants::imageExtendedVerInf, "ExtendedVersion"); |
| |
| auto l_imageExtendedVersion = std::get_if<std::string>(&l_retExVer); |
| if (!l_imageExtendedVersion) |
| { |
| throw DbusException( |
| "Unable to read extended version for the functional image [" + |
| l_imagePath + "]"); |
| } |
| |
| if ((*l_imageExtendedVersion).empty() || |
| (*l_imageExtendedVersion).length() <= constants::VALUE_2) |
| { |
| throw DbusException("Invalid extended version read for path [" + |
| l_imagePath + "]"); |
| } |
| |
| // return first two character from image name. |
| return (*l_imageExtendedVersion) |
| .substr(constants::VALUE_0, constants::VALUE_2); |
| } |
| throw std::runtime_error("No Image found with required priority."); |
| } |
| catch (const std::exception& l_ex) |
| { |
| logging::logMessage(l_ex.what()); |
| return std::string{}; |
| } |
| } |
| |
| /** |
| * @brief API to read DBus present property for the given inventory. |
| * |
| * @param[in] i_invObjPath - Inventory path. |
| * @return Present property value, false in case of any error. |
| */ |
| inline bool isInventoryPresent(const std::string& i_invObjPath) |
| { |
| if (i_invObjPath.empty()) |
| { |
| return false; |
| } |
| |
| const auto& l_retValue = |
| dbusUtility::readDbusProperty(constants::pimServiceName, i_invObjPath, |
| constants::inventoryItemInf, "Present"); |
| |
| auto l_ptrPresence = std::get_if<bool>(&l_retValue); |
| if (!l_ptrPresence) |
| { |
| return false; |
| } |
| |
| return (*l_ptrPresence); |
| } |
| } // namespace dbusUtility |
| } // namespace vpd |