diff options
author | Vishwanatha <vishwa@linux.vnet.ibm.com> | 2016-08-30 17:17:13 +0530 |
---|---|---|
committer | Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com> | 2016-11-09 14:22:59 +0530 |
commit | 81ee91f4507fe5a75df9dfa21fc406813a09a7ff (patch) | |
tree | a6f88287ba89fe74fafc784f74aeca8495efb0fd | |
parent | 008d34953af3a33f41dd009d71f2876946ef7e6f (diff) | |
download | phosphor-time-manager-81ee91f4507fe5a75df9dfa21fc406813a09a7ff.tar.gz phosphor-time-manager-81ee91f4507fe5a75df9dfa21fc406813a09a7ff.zip |
Add TimeManager daemon to openbmc
Time Manager daemon supporting NTP and MANUAL modes and below policy
*) BMC owns the time
*) HOST owns the time
*) SPLIT clock and HOST's time is maintained as an offset to BMC
*) BOTH the BMC and HOST own the clock
Change-Id: I81701b67731aa4b37d6926d5b93d397fea96e086
Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
-rw-r--r-- | Makefile.am | 11 | ||||
-rwxr-xr-x | bootstrap.sh | 16 | ||||
-rw-r--r-- | configure.ac | 25 | ||||
-rw-r--r-- | time-config.cpp | 590 | ||||
-rw-r--r-- | time-config.hpp | 313 | ||||
-rw-r--r-- | time-manager.cpp | 912 | ||||
-rw-r--r-- | time-manager.hpp | 303 | ||||
-rw-r--r-- | time-register.c | 25 | ||||
-rw-r--r-- | time-register.hpp | 17 |
9 files changed, 2212 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..090ee2c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,11 @@ +AM_DEFAULT_SOURCE_EXT = .cpp + +sbin_PROGRAMS = \ + timemanager + +timemanager_SOURCES = \ + time-register.c \ + time-config.cpp \ + time-manager.cpp + +timemanager_LDFLAGS = $(SYSTEMD_LIBS) $(libmapper_LIBS) diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..412550f --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile config.* \ + configure depcomp install-sh ltmain.sh missing *libtool" + +case $1 in + clean) + test -f Makefile && make maintainer-clean + rm -rf ${AUTOCONF_FILES} + + exit 0 + ;; +esac + +autoreconf -i +echo 'Run "./configure ${CONFIGURE_FLAGS} && make"' diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f3f119b --- /dev/null +++ b/configure.ac @@ -0,0 +1,25 @@ +# Initialization +AC_PREREQ([2.69]) +AC_INIT([phosphor-time-manager], [1.0], [https://github.com/openbmc/phosphor-time-manager/issues]) +AC_CONFIG_HEADERS([config.h]) +AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz]) + +# Checks for programs. +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX_14([noext]) +AC_PROG_CC +AM_PROG_AR +AC_PROG_INSTALL +AC_PROG_MAKE_SET + +# Checks for libraries. +AC_CHECK_LIB([mapper], [mapper_get_service]) +PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221]) + +# Checks for header files. +AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])]) + +# Checks for typedefs, structures, and compiler characteristics. + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/time-config.cpp b/time-config.cpp new file mode 100644 index 0000000..ff62782 --- /dev/null +++ b/time-config.cpp @@ -0,0 +1,590 @@ +#include <cstdlib> +#include <fstream> +#include <iostream> +#include <memory> +#include <mapper.h> +#include "time-manager.hpp" + +std::map<std::string, TimeConfig::FUNCTOR> TimeConfig::iv_TimeParams = { + { "time_mode", std::make_tuple(&TimeConfig::getSystemSettings, + &TimeConfig::updateTimeMode) + }, + + { "time_owner", std::make_tuple(&TimeConfig::getSystemSettings, + &TimeConfig::updateTimeOwner) + }, + + { "use_dhcp_ntp", std::make_tuple(&TimeConfig::getSystemSettings, + &TimeConfig::updateNetworkSettings) + } +}; + +TimeConfig::TimeConfig() : + iv_CurrTimeMode(timeModes::NTP), + iv_RequestedTimeMode(timeModes::NTP), + iv_CurrTimeOwner(timeOwners::BMC), + iv_RequestedTimeOwner(timeOwners::BMC), + iv_CurrDhcpNtp("yes"), + iv_SettingChangeAllowed(false), + iv_SplitModeChanged(false), + iv_dbus(nullptr) +{ + // Not really having anything to do here. +} + +// Given a mode string, returns it's equivalent mode enum +TimeConfig::timeModes TimeConfig::getTimeMode(const char* timeMode) +{ + // We are forcing the values to be in specific case and range + if (!strcmp(timeMode,"NTP")) + { + return timeModes::NTP; + } + else + { + return timeModes::MANUAL; + } +} + +// Accepts a timeMode enum and returns it's string value +const char* TimeConfig::modeStr(const TimeConfig::timeModes timeMode) +{ + switch(timeMode) + { + case timeModes::NTP: + { + return "NTP"; + } + case timeModes::MANUAL: + { + return "MANUAL"; + } + } +} + +// Given a owner string, returns it's equivalent owner enum +TimeConfig::timeOwners TimeConfig::getTimeOwner(const char* timeOwner) +{ + if (!strcmp(timeOwner,"BMC")) + { + return timeOwners::BMC; + } + else if (!strcmp(timeOwner,"HOST")) + { + return timeOwners::HOST; + } + else if (!strcmp(timeOwner,"SPLIT")) + { + return timeOwners::SPLIT; + } + else + { + return timeOwners::BOTH; + } +} + +// Accepts a timeOwner enum and returns it's string value +const char* TimeConfig::ownerStr(const timeOwners timeOwner) +{ + switch(timeOwner) + { + case timeOwners::BMC: + { + return "BMC"; + } + case timeOwners::HOST: + { + return "HOST"; + } + case timeOwners::SPLIT: + { + return "SPLIT"; + } + case timeOwners::BOTH: + { + return "BOTH"; + } + } +} + +// Returns the busname that hosts objPath +std::unique_ptr<char> TimeConfig::getProvider(const char* objPath) +{ + char *provider = nullptr; + mapper_get_service(iv_dbus, objPath, &provider); + return std::unique_ptr<char>(provider); +} + +// Accepts a settings name and returns its value. +// for the variant of type 'string' now. +std::string TimeConfig::getSystemSettings(const char* key) +{ + constexpr auto settingsObj = "/org/openbmc/settings/host0"; + constexpr auto propertyIntf = "org.freedesktop.DBus.Properties"; + constexpr auto hostIntf = "org.openbmc.settings.Host"; + + const char* value = nullptr; + std::string settingsVal {}; + sd_bus_message* reply = nullptr; + + std::cout <<"Getting System Settings: " << key << std::endl; + + // Get the provider from object mapper + auto settingsProvider = getProvider(settingsObj); + if (!settingsProvider) + { + std::cerr << "Error Getting service for Settings" << std::endl; + return value; + } + + auto r = sd_bus_call_method(iv_dbus, + settingsProvider.get(), + settingsObj, + propertyIntf, + "Get", + nullptr, + &reply, + "ss", + hostIntf, + key); + if (r < 0) + { + std::cerr <<"Error" << strerror(-r) + <<" reading system settings" << std::endl; + goto finish; + } + + r = sd_bus_message_read(reply, "v", "s", &value); + if (r < 0) + { + std::cerr <<"Error " << strerror(-r) + <<" parsing settings data" << std::endl; + } +finish: + if (value) + { + settingsVal.assign(value); + } + reply = sd_bus_message_unref(reply); + return settingsVal; +} + +// Reads value from /org/openbmc/control/power0 +// This signature on purpose to plug into time parameter map +std::string TimeConfig::getPowerSetting(const char* key) +{ + constexpr auto powerObj = "/org/openbmc/control/power0"; + constexpr auto powerIntf = "org.openbmc.control.Power"; + constexpr auto propertyIntf = "org.freedesktop.DBus.Properties"; + + int value = -1; + std::string powerValue {}; + sd_bus_message* reply = nullptr; + + std::cout <<"Reading Power Control key: " << key << std::endl; + + // Get the provider from object mapper + auto powerProvider = getProvider(powerObj); + if (!powerProvider) + { + std::cerr <<" Error getting provider for Power Settings" << std::endl; + return powerValue; + } + + auto r = sd_bus_call_method(iv_dbus, + powerProvider.get(), + powerObj, + propertyIntf, + "Get", + nullptr, + &reply, + "ss", + powerIntf, + key); + if (r < 0) + { + std::cerr <<"Error " << strerror(-r) + << "reading: " << key << std::endl; + goto finish; + } + + r = sd_bus_message_read(reply, "v", "i", &value); + if (r < 0) + { + std::cerr <<"Error " << strerror(-r) + <<" parsing " << key << "value" << std::endl; + // For maintenance + goto finish; + } +finish: + if (value != -1) + { + powerValue = std::to_string(value); + } + reply = sd_bus_message_unref(reply); + return powerValue; +} + +// Updates .network file with UseNtp= +int TimeConfig::updateNetworkSettings(const std::string& useDhcpNtp) +{ + constexpr auto networkObj = "/org/openbmc/NetworkManager/Interface"; + constexpr auto networkIntf = "org.openbmc.NetworkManager"; + + std::cout << "use_dhcp_ntp = " << useDhcpNtp.c_str() << std::endl; + + // If what we have already is what it is, then just return. + if (iv_CurrDhcpNtp == useDhcpNtp) + { + return 0; + } + + // Get the provider from object mapper + auto networkProvider = getProvider(networkObj); + if (!networkProvider) + { + return -1; + } + + auto r = sd_bus_call_method(iv_dbus, + networkProvider.get(), + networkObj, + networkIntf, + "UpdateUseNtpField", + nullptr, + nullptr, + "s", + useDhcpNtp.c_str()); + if (r < 0) + { + std::cerr <<"Error " << strerror(-r) + << " updating UseNtp" << std::endl; + } + else + { + std::cout <<"Successfully updated UseNtp=[" + << useDhcpNtp << "]" << std::endl; + + r = writeData<decltype(useDhcpNtp)>(cv_DhcpNtpFile, useDhcpNtp); + } + + return 0; +} + +// Reads the values from 'settingsd' and applies: +// 1) Time Mode +// 2) time Owner +// 3) UseNTP setting +// 4) Pgood +int TimeConfig::processInitialSettings(sd_bus* dbus) +{ + // First call from TimeManager to config manager + iv_dbus = dbus; + + // Read saved info like Who was the owner , what was the mode, + // what was the use_dhcp_ntp setting before etc.. + auto r = readPersistentData(); + if (r < 0) + { + std::cerr << "Error reading the data saved in flash." + << std::endl; + return r; + } + + // Now read whats in settings and apply if allowed. + for (auto& iter : iv_TimeParams) + { + // Get the settings value for various keys. + auto reader = std::get<READER>(iter.second); + auto value = (this->*reader)(iter.first.c_str()); + if (!value.empty()) + { + // Get the value for the key and validate. + auto updater = std::get<UPDATER>(iter.second); + auto r = (this->*updater)(value); + if (r < 0) + { + std::cerr << "Error setting up initial keys" << std::endl; + return r; + } + } + else + { + std::cerr << "key " << iter.first + <<" has no value: " << std::endl; + return -1; + } + } + + // Now that we have taken care of consuming, push this as well + // so that we can use the same map for handling pgood change too. + auto readerUpdater = std::make_tuple(&TimeConfig::getPowerSetting, + &TimeConfig::processPgoodChange); + iv_TimeParams.emplace("pgood", readerUpdater); + + return 0; +} + +// This is called by Property Change handler on the event of +// receiving notification on property value change. +int TimeConfig::updatePropertyVal(const char* key, const std::string& value) +{ + auto iter = iv_TimeParams.find(key); + if (iter != iv_TimeParams.end()) + { + auto updater = std::get<UPDATER>(iter->second); + return (this->*updater)(value); + } + // Coming here indicates that we never had a matching key. + return -1; +} + +// Called by sd_event when Properties are changed in Control/power0 +// Interested in change to 'pgood' +int TimeConfig::processPgoodChange(const std::string& newPgood) +{ + // Indicating that we are safe to apply any changes + if (!newPgood.compare("0")) + { + iv_SettingChangeAllowed = true; + std::cout <<"Changing time settings allowed now" << std::endl; + } + else + { + iv_SettingChangeAllowed = false; + std::cout <<"Changing time settings is *deferred* now" << std::endl; + } + + // if we have had users that changed the time settings + // when we were not ready yet, do it now. + if (iv_RequestedTimeOwner != iv_CurrTimeOwner) + { + auto r = updateTimeOwner(ownerStr(iv_RequestedTimeOwner)); + if (r < 0) + { + std::cerr << "Error updating new time owner" << std::endl; + return r; + } + std::cout << "New Owner is : " + << ownerStr(iv_RequestedTimeOwner) << std::endl; + } + + if (iv_RequestedTimeMode != iv_CurrTimeMode) + { + auto r = updateTimeMode(modeStr(iv_RequestedTimeMode)); + if (r < 0) + { + std::cerr << "Error updating new time mode" << std::endl; + return r; + } + std::cout <<"New Mode is : " + << modeStr(iv_RequestedTimeMode) << std::endl; + } + return 0; +} + +// Manipulates time owner if the system setting allows it +int TimeConfig::updateTimeMode(const std::string& newModeStr) +{ + auto r = 0; + iv_RequestedTimeMode = getTimeMode(newModeStr.c_str()); + + std::cout <<"Requested_Mode: " << newModeStr + << " Current_Mode: " << modeStr(iv_CurrTimeMode) + << std::endl; + + if (iv_RequestedTimeMode == iv_CurrTimeMode) + { + std::cout << "Mode is already set to : " + << newModeStr << std::endl; + return 0; + } + + // Also, if the time owner is HOST, then we should not allow NTP. + // However, it may so happen that there are 2 pending requests, one for + // changing to NTP and other for changing owner to something not HOST. + // So check if there is a pending timeOwner change and if so, allow NTP + // if the current is HOST and requested is non HOST. + if (iv_CurrTimeOwner == timeOwners::HOST && + iv_RequestedTimeOwner == timeOwners::HOST && + iv_RequestedTimeMode == timeModes::NTP) + { + std::cout <<"Can not set mode to NTP with HOST as owner" + << std::endl; + return 0; + } + + if (iv_SettingChangeAllowed) + { + r = modifyNtpSettings(iv_RequestedTimeMode); + if (r < 0) + { + std::cerr << "Error changing TimeMode settings" + << std::endl; + } + else + { + iv_CurrTimeMode = iv_RequestedTimeMode; + } + std::cout << "Current_Mode changed to: " + << newModeStr << " :: " << modeStr(iv_CurrTimeMode) << std::endl; + + // Need this when we either restart or come back from reset + r = writeData(cv_TimeModeFile, modeStr(iv_CurrTimeMode)); + } + else + { + std::cout <<"Deferring update until system state allows it" + << std::endl; + } + return r; +} + +// Manipulates time owner if the system setting allows it +int TimeConfig::updateTimeOwner(const std::string& newOwnerStr) +{ + int r = 0; + iv_RequestedTimeOwner = getTimeOwner(newOwnerStr.c_str()); + + // Needed when owner changes to HOST + std::string manualMode = "Manual"; + + // Needed by time manager to do some house keeping + iv_SplitModeChanged = false; + + if (iv_RequestedTimeOwner == iv_CurrTimeOwner) + { + std::cout <<"Owner is already set to : " + << newOwnerStr << std::endl; + return 0; + } + + std::cout <<"Requested_Owner: " << newOwnerStr + << " Current_Owner: " << ownerStr(iv_CurrTimeOwner) + << std::endl; + + if (iv_SettingChangeAllowed) + { + // If we are transitioning from SPLIT to something else, + // reset the host offset. + if (iv_CurrTimeOwner == timeOwners::SPLIT && + iv_RequestedTimeOwner != timeOwners::SPLIT) + { + // Needed by time manager to do some house keeping + iv_SplitModeChanged = true; + } + iv_CurrTimeOwner = iv_RequestedTimeOwner; + std::cout << "Current_Owner is now: " + << newOwnerStr << std::endl; + + // HOST and NTP are exclusive + if (iv_CurrTimeOwner == timeOwners::HOST) + { + std::cout <<"Forcing the mode to MANUAL" << std::endl; + r = updateTimeMode(manualMode); + if (r < 0) + { + std::cerr << "Error forcing the mode to MANUAL" << std::endl; + return r; + } + } + // Need this when we either restart or come back from reset + r = writeData(cv_TimeOwnerFile, ownerStr(iv_CurrTimeOwner)); + } + else + { + std::cout <<"Deferring update until system state allows it" + << std::endl; + } + + return r; +} + +// Accepts the time mode and makes necessary changes to timedate1 +int TimeConfig::modifyNtpSettings(const timeModes& newTimeMode) +{ + auto ntpChangeOp = 0; + + // Pass '1' -or- '0' to SetNTP method indicating Enable/Disable + ntpChangeOp = (newTimeMode == timeModes::NTP) ? 1 : 0; + + std::cout <<"Applying NTP setting..." << std::endl; + + auto r = sd_bus_call_method(iv_dbus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetNTP", + nullptr, + nullptr, // timedate1 does not return response + "bb", + ntpChangeOp, // '1' means Enable + 0); // '0' meaning no policy-kit + if (r < 0) + { + std::cerr <<"Error: " << strerror(-r) + << "changing time Mode" << std::endl; + } + else + { + std::cout << "SUCCESS. NTP setting is now: " << + (ntpChangeOp) ? "Enabled" : "Disabled"; + + // TODO : https://github.com/openbmc/phosphor-time-manager/issues/1 + if (ntpChangeOp) + { + system("systemctl restart systemd-timesyncd &"); + } + else + { + system("systemctl stop systemd-timesyncd &"); + } + } + return r; +} + +// Reads all the saved data from the last run +int TimeConfig::readPersistentData() +{ + // If we are coming back from a reset reload, then need to + // read what was the last successful Mode and Owner. + auto savedTimeMode = readData<std::string>(cv_TimeModeFile); + if (!savedTimeMode.empty()) + { + iv_CurrTimeMode = getTimeMode(savedTimeMode.c_str()); + std::cout <<"Last known time_mode: " + << savedTimeMode.c_str() << std::endl; + } + + auto savedTimeOwner = readData<std::string>(cv_TimeOwnerFile); + if (!savedTimeOwner.empty()) + { + iv_CurrTimeOwner = getTimeOwner(savedTimeOwner.c_str()); + std::cout <<"Last known time_owner: " + << savedTimeOwner.c_str() << std::endl; + } + + auto savedDhcpNtp = readData<std::string>(cv_DhcpNtpFile); + if (!savedDhcpNtp.empty()) + { + iv_CurrDhcpNtp = savedDhcpNtp; + std::cout <<"Last known use_dhcp_ntp: " + << iv_CurrDhcpNtp.c_str() << std::endl; + } + else + { + // This seems to be the first time. + std::cerr <<"Empty DhcpNtp string" << std::endl; + iv_CurrDhcpNtp = "yes"; + } + + // Doing this here to make sure 'pgood' is read and handled + // first before anything. + auto pgood = getPowerSetting("pgood"); + if (!pgood.compare("0")) + { + std::cout << "Changing settings *allowed* now" << std::endl; + iv_SettingChangeAllowed = true; + } + return 0; +} diff --git a/time-config.hpp b/time-config.hpp new file mode 100644 index 0000000..0f08641 --- /dev/null +++ b/time-config.hpp @@ -0,0 +1,313 @@ +#include <map> +#include <systemd/sd-bus.h> + +/** @class TimeConfig + * @brief Maintains various time modes and time owners. + */ +class TimeConfig +{ +public: + /** @brief Supported time modes + * NTP Time sourced by Network Time Server + * MANUAL User of the system need to set the time + */ + enum class timeModes + { + NTP, + MANUAL + }; + + /** @brief Supported time owners + * BMC Time source may be NTP or MANUAL but it has to be set natively + * on the BMC. Meaning, host can not set the time. What it also + * means is that when BMC gets IPMI_SET_SEL_TIME, then its ignored. + * similarly, when BMC gets IPMI_GET_SEL_TIME, then the BMC's time + * is returned. + * + * HOST Its only IPMI_SEL_SEL_TIME that will set the time on BMC. + * Meaning, IPMI_GET_SEL_TIME and request to get BMC time will + * result in same value. + * + * SPLIT Both BMC and HOST will maintain their individual clocks but then + * the time information is stored in BMC. BMC can have either NTP + * or MANUAL as it's source of time and will set the time directly + * on the BMC. When IPMI_SET_SEL_TIME is received, then the delta + * between that and BMC's time is calculated and is stored. + * When BMC reads the time, the current time is returned. + * When IPMI_GET_SEL_TIME is received, BMC's time is retrieved and + * then the delta offset is factored in prior to returning. + * + * BOTH: BMC's time is set with whoever that sets the time. Similarly, + * BMC's time is returned to whoever that asks the time. + */ + enum class timeOwners + { + BMC, + HOST, + SPLIT, + BOTH + }; + + // Do not have a usecase of copying this object so disable + TimeConfig(); + ~TimeConfig() = default; + TimeConfig(const TimeConfig&) = delete; + TimeConfig& operator=(const TimeConfig&) = delete; + TimeConfig(TimeConfig&&) = delete; + TimeConfig& operator=(TimeConfig&&) = delete; + + inline auto getCurrTimeMode() const + { + return iv_CurrTimeMode; + } + + inline auto getCurrTimeOwner() const + { + return iv_CurrTimeOwner; + } + + inline auto getRequestedTimeMode() const + { + return iv_RequestedTimeMode; + } + + inline auto getRequestedTimeOwner() const + { + return iv_RequestedTimeOwner; + } + + inline auto isSplitModeChanged() const + { + return iv_SplitModeChanged; + } + + inline void updateSplitModeFlag(const bool& value) + { + iv_SplitModeChanged = value; + } + + inline sd_bus* getDbus() const + { + return iv_dbus; + } + + /** brief Generic file reader used to read time mode, + * time owner, host time, host offset and useDhcpNtp + * + * @param[in] filename - Name of file where data is preserved. + * @return - File content + */ + template <typename T> + T readData(const char* fileName) + { + T data = T(); + if(std::ifstream(fileName)) + { + std::ifstream file(fileName, std::ios::in); + file >> data; + file.close(); + } + return data; + } + + /** @brief Generic file writer used to write time mode, + * time owner, host time, host offset and useDhcpNtp + * + * @param[in] filename - Name of file where data is preserved. + * @param[in] data - Data to be written to file + * @return - 0 for now. But will be changed to raising + * - Exception + */ + template <typename T> + auto writeData(const char* fileName, const T&& data) + { + std::ofstream file(fileName, std::ios::out); + file << data; + file.close(); + return 0; + } + + /** @brief Reads saved data and populates below properties + * - Current Time Mode + * - Current Time Owner + * - Whether to use NTP settings given by DHCP + * - Last known host offset + * + * @param[in] dbus - Handler to sd_bus used by time manager + * + * @return - < 0 for failure and others for success + */ + int processInitialSettings(sd_bus* dbus); + + /** @brief Accepts time mode string, returns the equivalent enum + * + * @param[in] timeModeStr - Current Time Mode in string + * + * @return - Equivalent ENUM + * - Input : "NTP", Output: timeModes::NTP + */ + static timeModes getTimeMode(const char* timeModeStr); + + /** @brief Accepts timeMode enum and returns it's string equivalent + * + * @param[in] timeMode - Current Time Mode Enum + * + * @return - Equivalent string + * - Input : timeModes::NTP, Output : "NTP" + */ + static const char* modeStr(const timeModes timeMode); + + /** @brief Accepts timeOwner string and returns it's equivalent enum + * + * @param[in] timeOwnerStr - Current Time Owner in string + * + * @return - Equivalent ENUM + * - Input : "BMC", output : timeOwners::BMC + */ + static timeOwners getTimeOwner(const char* timeOwnerStr); + + /** @brief Accepts timeOwner enum and returns it's string equivalent + * + * @param[in] timeOwner - Current Time Mode Enum + * + * @return - Equivalent string + * - Input : timeOwners::BMC, Output : "BMC" + */ + static const char* ownerStr(const timeOwners timeOwner); + + /** @brief Gets called when the settings property changes. + * Walks the map and then applies the changes + * + * @param[in] key - Name of the property + * @param[in] value - Value + * + * @return - < 0 on failure, success otherwise + */ + int updatePropertyVal(const char* key, const std::string& value); + + // Acts on the time property changes / reads initial property + using READER = std::string (TimeConfig::*) (const char*); + using UPDATER = int (TimeConfig::*) (const std::string&); + using FUNCTOR = std::tuple<READER, UPDATER>; + + // Most of this is statically constructed and PGOOD is added later. + static std::map<std::string, FUNCTOR> iv_TimeParams; + +private: + // Bus initialised by manager on a call to process initial settings + sd_bus *iv_dbus; + + /** @brief 'Requested' is what is asked by user in settings + * 'Current' is what the TimeManager is really using since its not + * possible to apply the mode and owner as and when the user updates + * Settings. They are only applied when the system power is off. + */ + timeModes iv_CurrTimeMode; + timeModes iv_RequestedTimeMode; + + timeOwners iv_CurrTimeOwner; + timeOwners iv_RequestedTimeOwner; + + /** @brief One of the entry in .network file indicates whether the + * systemd-timesyncd should use the NTP server list that are sent by DHCP + * server or not. If the value is 'yes', then NTP server list sent by DHCP + * are used. else entries that are configured statically with NTP= are used + */ + std::string iv_CurrDhcpNtp; + + /** @brief Dictated by state of pgood. When the pgood value is 'zero', then + * its an indication that we are okay to apply any pending Mode / Owner + * values. Meaning, Current will be updated with what is in Requested. + */ + bool iv_SettingChangeAllowed; + + // Needed to nudge Time Manager to reset offset + bool iv_SplitModeChanged; + + static constexpr auto cv_TimeModeFile = "/var/lib/obmc/saved_timeMode"; + static constexpr auto cv_TimeOwnerFile = "/var/lib/obmc/saved_timeOwner"; + static constexpr auto cv_DhcpNtpFile = "/var/lib/obmc/saved_dhcpNtp"; + + /** @brief Wrapper that looks up in the mapper service for a given + * object path and returns the service name + * + * @param[in] objpath - dbus object path + * + * @return - unique_ptr holding service name. + * - Caller needs to validate if its populated + */ + std::unique_ptr<char> getProvider(const char* objPath); + + /** @brief Helper function for processInitialSettings. + * Reads saved data and populates below properties. Only the values are + * read from the file system. but it will not take the action on those. + * Actions on these are taken by processInitialSettings + * + * - Current Time Mode + * - Current Time Owner + * - Whether to use NTP settings given by DHCP + * - Current Pgood state + * + * @return - < 0 for failure and success on others + */ + int readPersistentData(); + + /** @brief Updates the 'ntp' field in systemd/timedate1, + * which enables / disables NTP syncing + * + * @param[in] newTimeMode - Time Mode Enum + * + * @return - < 0 on failure and success on others + */ + int modifyNtpSettings(const timeModes& newTimeMode); + + /** @brief Accepts system setting parameter and returns its value + * + * @param[in] key - Name of the property + * + * @return - Value as string + */ + std::string getSystemSettings(const char* key); + + /** @brief Reads the data hosted by /org/openbmc/control/power0 + * + * @param[in] key - Name of the property + * + * @return - Value as string + */ + std::string getPowerSetting(const char* key); + + /** @brief Accepts Mode string and applies only if conditions allow it. + * + * @param[in] newModeStr - Requested Time Mode + * + * @return - < 0 on failure, success otherwise + */ + int updateTimeMode(const std::string& newModeStr); + + /** @brief Accepts Ownere string and applies only if conditions allow it. + * + * @param[in] newOwnerStr - Requested Time Owner + * + * @return - < 0 on failure, success otherwise + */ + + int updateTimeOwner(const std::string& newownerStr); + + /** @brief Updates .network file with UseNtp= provided by NetworkManager + * + * @param[in] useDhcpNtp - will be 'yes' or 'no' + * + * @return - < 0 on failure, success otherwise + */ + int updateNetworkSettings(const std::string& useDhcpNtp); + + /** @brief Accepts current pgood value and then updates any pending mode + * or owner requests + * + * @param[in] useDhcpNtp - will be 'yes' or 'no' + * + * @return - < 0 on failure, success otherwise + */ + int processPgoodChange(const std::string& newPgood); +}; diff --git a/time-manager.cpp b/time-manager.cpp new file mode 100644 index 0000000..bde2e0d --- /dev/null +++ b/time-manager.cpp @@ -0,0 +1,912 @@ +#define _XOPEN_SOURCE +#include <chrono> +#include <sstream> +#include <iostream> +#include <iomanip> +#include <array> +#include <unistd.h> +#include <assert.h> +#include <sys/timerfd.h> +#include <systemd/sd-event.h> +#include <systemd/sd-bus.h> +#include <systemd/sd-daemon.h> +#include "time-register.hpp" +#include "time-manager.hpp" + +// Neeed to do this since its not exported outside of the kernel. +// Refer : https://gist.github.com/lethean/446cea944b7441228298 +#ifndef TFD_TIMER_CANCEL_ON_SET +#define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif + +// Needed to make sure timerfd does not misfire eventhough we set CANCEL_ON_SET +#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1) + +// Used in time-register.c +extern sd_bus_vtable timeServicesVtable[]; + +Time::Time(const TimeConfig& timeConfig) : config(timeConfig) +{ + // Nothing to do here +} + +BmcTime::BmcTime(const TimeConfig& timeConfig) : Time(timeConfig) +{ + // Nothing to do here +} + +HostTime::HostTime(const TimeConfig& timeConfig, + const std::chrono::microseconds& hostOffset) + : Time(timeConfig), + iv_Offset(hostOffset) +{ + // Nothing to do here +} + +TimeManager::TimeManager() : + iv_HostOffset(std::chrono::microseconds(0)), + iv_UptimeUsec(std::chrono::microseconds(0)), + iv_EventSource(nullptr), + iv_Event(nullptr) +{ + assert(setupTimeManager() >= 0); +} + +// Needed to be standalone extern "C" to register +// as a callback routine with sd_bus_vtable +int GetTime(sd_bus_message* m, void* userdata, + sd_bus_error* retError) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + return tmgr->getTime(m, userdata, retError); +} + +int SetTime(sd_bus_message* m, void* userdata, + sd_bus_error* retError) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + return tmgr->setTime(m, userdata, retError); +} + +// Property reader +int getCurrTimeModeProperty(sd_bus* bus, const char* path, + const char* interface, const char* property, + sd_bus_message* m, void* userdata, + sd_bus_error* error) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + return sd_bus_message_append(m, "s", + TimeConfig::modeStr(tmgr->config.getCurrTimeMode())); +} + +int getCurrTimeOwnerProperty(sd_bus* bus, const char* path, + const char* interface, const char* property, + sd_bus_message* m, void* userdata, + sd_bus_error* error) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + return sd_bus_message_append(m, "s", + TimeConfig::ownerStr(tmgr->config.getCurrTimeOwner())); +} + +int getReqTimeModeProperty(sd_bus* bus, const char* path, + const char* interface, const char* property, + sd_bus_message* m, void* userdata, + sd_bus_error* error) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + return sd_bus_message_append(m, "s", + TimeConfig::modeStr(tmgr->config.getRequestedTimeMode())); +} + +int getReqTimeOwnerProperty(sd_bus* bus, const char* path, + const char* interface, const char* property, + sd_bus_message* m, void* userdata, + sd_bus_error* error) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + return sd_bus_message_append(m, "s", + TimeConfig::ownerStr(tmgr->config.getRequestedTimeOwner())); +} + +int TimeManager::getTime(sd_bus_message* m, void* userdata, + sd_bus_error* retError) +{ + const char* target = nullptr; + + // Extract the target and call respective GetTime + auto r = sd_bus_message_read(m, "s", &target); + if (r < 0) + { + std::cerr << "Error:" << strerror(-r) + <<" reading user time" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_IO_ERROR, "Error reading input"); + + return sd_bus_reply_method_error(m, retError); + } + + if (!strcasecmp(target, "bmc")) + { + auto time = BmcTime(config); + return time.getTime(m, retError); + } + else if (!strcasecmp(target, "host")) + { + auto time = HostTime(config, iv_HostOffset); + return time.getTime(m, retError); + } + else + { + std::cerr << "Error:" << strerror(-r) + <<" Invalid Target" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_IO_ERROR, "Valid targets are BMC or HOST"); + + return sd_bus_reply_method_error(m, retError); + } +} + +int TimeManager::setTime(sd_bus_message* m, void* userdata, + sd_bus_error* retError) +{ + long long int timeInUsec {}; + + const char* target = nullptr; + auto r = sd_bus_message_read(m, "s", &target); + if (r < 0) + { + std::cerr << "Error:" << strerror(-r) + <<" reading user time" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_IO_ERROR, "Error reading input"); + + return sd_bus_reply_method_error(m, retError); + } + + if (!strcasecmp(target, "bmc")) + { + auto time = BmcTime(config); + auto r = time.setTime(m, retError); + if (r < 0) + { + // This would have the error populated + return sd_bus_reply_method_error(m, retError); + } + } + else if (!strcasecmp(target, "host")) + { + auto time = HostTime(config, iv_HostOffset); + + auto r = time.setTime(m, retError); + if (r < 0) + { + // This would have the error populated + return sd_bus_reply_method_error(m, retError); + } + + if (config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT) + { + iv_HostOffset = time.getChangedOffset(); + r = config.writeData<decltype(iv_HostOffset.count())> + (cv_HostOffsetFile, + iv_HostOffset.count()); + if (r < 0) + { + // probably does not make sense to crash on these.. + // The next NTP sync will set things right. + std::cerr << "Error saving host_offset: " + << iv_HostOffset.count() << std::endl; + } + } + } + return sd_bus_reply_method_return(m, "i", 0); +} + +int Time::setTimeOfDay(const std::chrono::microseconds& timeOfDayUsec) +{ + // These 2 are for bypassing some policy + // checking in the timedate1 service + auto relative = false; + auto interactive = false; + + return sd_bus_call_method(config.getDbus(), + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetTime", + nullptr, + nullptr, // timedate1 does not return response + "xbb", + (int64_t)timeOfDayUsec.count(), //newTimeUsec, + relative, // Time in absolute seconds since epoch + interactive); // bypass polkit checks +} + +// Common routine for BMC and HOST Get Time operations +std::chrono::microseconds Time::getBaseTime() +{ + auto currBmcTime = std::chrono::system_clock::now(); + return std::chrono::duration_cast<std::chrono::microseconds> + (currBmcTime.time_since_epoch()); +} + +// Accepts the time in microseconds and converts to Human readable format. +std::string Time::convertToStr(const std::chrono::microseconds& timeInUsec) +{ + using namespace std::chrono; + + // Convert this to number of seconds; + auto timeInSec = duration_cast<seconds>(microseconds(timeInUsec)); + auto time_T = static_cast<std::time_t>(timeInSec.count()); + + std::ostringstream timeFormat {}; + timeFormat << std::put_time(std::gmtime(&time_T), "%c %Z"); + + auto timeStr = timeFormat.str(); + std::cout << timeStr.c_str() << std::endl; + return timeStr; +} + +// Reads timeofday and returns time string +// and also number of microseconds. +// Ex : Tue Aug 16 22:49:43 2016 +int BmcTime::getTime(sd_bus_message *m, sd_bus_error *retError) +{ + std::cout << "Request to get BMC time: "; + + // Get BMC time + auto timeInUsec = getBaseTime(); + auto timeStr = convertToStr(timeInUsec); + return sd_bus_reply_method_return( + m, "sx", timeStr.c_str(), (uint64_t)timeInUsec.count()); +} + +// Designated to be called by IPMI_GET_SEL time from host +int HostTime::getTime(sd_bus_message *m, sd_bus_error *retError) +{ + using namespace std::chrono; + + std::cout << "Request to get HOST time" << std::endl; + + // Get BMC time and add Host's offset + // Referencing the iv_hostOffset of TimeManager object + auto timeInUsec = getBaseTime(); + auto hostTime = timeInUsec.count() + iv_Offset.count(); + + auto timeStr = convertToStr(duration_cast<microseconds> + (microseconds(hostTime))); + + std::cout << " Host_time_str: [ " << timeStr + << " ] host_time usec: [ " << hostTime + << " ] host_offset: [ " << iv_Offset.count() + << " ] " << std::endl; + + return sd_bus_reply_method_return(m, "sx", timeStr.c_str(), + (uint64_t)hostTime); + return 0; +} + +// Gets the time string and verifies if it conforms to format %Y-%m-%d %H:%M:%S +// and then sets the BMC time. If the input time string does not conform to the +// format, an error message is returned. +int BmcTime::setTime(sd_bus_message *m, sd_bus_error *retError) +{ + tm userTm {}; + const char* userTimeStr = nullptr; + + std::cout << "Request to set BMC time" << std::endl; + + std::cout << "Curr_Mode: " << TimeConfig::modeStr(config.getCurrTimeMode()) + << " Curr_Owner: " << TimeConfig::ownerStr(config.getCurrTimeOwner()) + << std::endl; + + if (config.getCurrTimeMode() == TimeConfig::timeModes::NTP) + { + std::cerr << "Can not set time. Mode is NTP" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_FAILED, "Current Mode is NTP"); + + return -1; + } + + if(config.getCurrTimeOwner() == TimeConfig::timeOwners::HOST) + { + std::cerr << "Can not set time. Owner is HOST" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_FAILED, "Current owner is HOST"); + + return -1; + } + + auto r = sd_bus_message_read(m, "s", &userTimeStr); + if (r < 0) + { + std::cerr << "Error:" << strerror(-r) + <<" reading user time" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_IO_ERROR, "Error reading input"); + return r; + } + + std::cout <<" BMC TIME : " << userTimeStr << std::endl; + + // Convert the time string into tm structure + std::istringstream timeString {}; + timeString.str(userTimeStr); + timeString >> std::get_time(&userTm, "%Y-%m-%d %H:%M:%S"); + if (timeString.fail()) + { + std::cerr << "Error: Incorrect time format" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_INVALID_ARGS, "Incorrect time format"); + return -1; + } + + // Convert the time structure into number of + // seconds maintained in GMT. Followed the same that is in + // systemd/timedate1 + auto timeOfDay = timegm(&userTm); + if (timeOfDay < 0) + { + std::cerr <<"Error converting tm to seconds" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_FAILED, "Error converting tm to seconds"); + return -1; + } + + // Set REALTIME and also update hwclock + auto timeInUsec = std::chrono::microseconds( + std::chrono::seconds(timeOfDay)); + r = setTimeOfDay(timeInUsec); + if (r < 0) + { + std::cerr <<"Error: " << strerror(-r) + << "setting time on BMC" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_FAILED, "Error setting time on BMC"); + } + return r < 0 ? r : 0; +} + +// Gets the time string from IPMI ( which is currently in seconds since epoch ) +// and then sets the BMC time / adjusts the offset depending on current owner +// policy. +int HostTime::setTime(sd_bus_message *m, sd_bus_error *retError) +{ + using namespace std::chrono; + + std::cout << "Request to SET Host time" << std::endl; + + std::cout << "Curr_Mode: " << TimeConfig::modeStr(config.getCurrTimeMode()) + << "Curr_Owner: " << TimeConfig::ownerStr(config.getCurrTimeOwner()) + << "host_offset: " << iv_Offset.count() << std::endl; + + if (config.getCurrTimeOwner() == TimeConfig::timeOwners::BMC) + { + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_FAILED, "Current Owner is BMC"); + return -1; + } + + const char* newHostTime = nullptr; + auto r = sd_bus_message_read(m, "s", &newHostTime); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "reading host time" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_IO_ERROR, "Error reading input"); + return -1; + } + + // We need to convert the string input to decimal. + auto hostTimeSec = std::stol(std::string(newHostTime)); + + // And then to microseconds + auto hostTimeUsec = duration_cast<microseconds>(seconds(hostTimeSec)); + std::cout << "setHostTime: HostTimeInUSec: " + << hostTimeUsec.count() << std::endl; + + if (config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT) + { + // Adjust the host offset + auto bmcTimeUsec = duration_cast<microseconds> + (system_clock::now().time_since_epoch()); + + // We are not doing any time settings in BMC + std::cout << "Updated: host_time: [ " << hostTimeUsec.count() + << " ] host_offset: [ " << iv_Offset.count() + << " ] " << std::endl; + + // Communicate the offset back to manager to update needed. + changedOffset = hostTimeUsec - bmcTimeUsec; + + return 0; + } + + // We are okay to update time in as long as BMC is not the owner + r = setTimeOfDay(hostTimeUsec); + if (r < 0) + { + std::cerr <<"Error: " << strerror(-r) + << "setting HOST time" << std::endl; + *retError = SD_BUS_ERROR_MAKE_CONST( + SD_BUS_ERROR_FAILED, "Error setting time"); + } + + return r < 0 ? r : 0; +} + +// Gets called into by sd_event on an activity seen on sd_bus +int TimeManager::processSdBusMessage(sd_event_source* es, int fd, + uint32_t revents, void* userdata) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + auto r = sd_bus_process(tmgr->getTimeBus(), nullptr); + if (r < 0) + { + std::cerr <<"Error: " << strerror(-r) + <<" processing sd_bus message:" << std::endl; + } + return r; +} + +// Gets called into by sd_event on any time SET event +int TimeManager::processTimeChange(sd_event_source* es, int fd, + uint32_t revents, void* userdata) +{ + using namespace std::chrono; + std::cout << "BMC time changed" << std::endl; + + auto tmgr = static_cast<TimeManager*>(userdata); + + std::array<char, 64> time {}; + + // We are not interested in the data here. Need to read time again . + // So read until there is something here in the FD + while (read(fd, time.data(), time.max_size()) > 0); + + std::cout <<" Curr_Mode: " << TimeConfig::modeStr(tmgr->config.getCurrTimeMode()) + << " Curr_Owner : " << TimeConfig::ownerStr(tmgr->config.getCurrTimeOwner()) + << " Host_Offset: " << tmgr->getHostOffset().count() << std::endl; + + // Read the current BMC time and adjust the + // host time offset if the mode is SPLIT + if (tmgr->config.getCurrTimeOwner() == TimeConfig::timeOwners::SPLIT) + { + // Delta between REAL and Monotonic. + auto uptimeUsec = duration_cast<microseconds> + (system_clock::now().time_since_epoch() - + steady_clock::now().time_since_epoch()); + + auto deltaTimeUsec = uptimeUsec - tmgr->getUptimeUsec(); + + // If the BMC time goes backwards, then - of - will handle that. + auto newHostOffset = tmgr->getHostOffset() - deltaTimeUsec; + tmgr->updateHostOffset(newHostOffset); + + std::cout << " UPDATED HOST_OFFSET: " + << tmgr->getHostOffset().count() << std::endl; + tmgr->updateUptimeUsec(uptimeUsec); + + // Persist this + auto r = tmgr->config.writeData<decltype(tmgr->getHostOffset().count())> + (TimeManager::cv_HostOffsetFile, + tmgr->getHostOffset().count()); + if (r < 0) + { + std::cerr << "Error saving host_offset: " + << tmgr->getHostOffset().count() << std::endl; + return r; + } + std::cout << " Updated: Host_Offset: " + << tmgr->getHostOffset().count() << std::endl; + } + return 0; +} + +// Resets iv_HostOffset. Needed when we move away from SPLIT. +int TimeManager::resetHostOffset() +{ + iv_HostOffset = std::chrono::microseconds(0); + auto r = config.writeData<decltype(iv_HostOffset.count())> + (cv_HostOffsetFile, + iv_HostOffset.count()); + config.updateSplitModeFlag(false); + return r; +} + +// Called by sd_event when Pgood is changed in Power +int TimeManager::processPgoodChange(sd_bus_message* m, void* userdata, + sd_bus_error* retError) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + const char* key = nullptr; + const char* value = nullptr; + + auto newPgood = -1; + auto r = 0; + + std::cout <<" PGOOD has changed.." << std::endl; + + // input data is "sa{sv}as" and we are just interested in a{sv} + r = sd_bus_message_skip(m, "s"); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) << + "skipping interface name in data" << std::endl; + return r; + } + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<"entering the dictionary" << std::endl; + return r; + } + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, + "sv")) > 0) + { + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<" reading the key from dict" << std::endl; + // Can not continue here since the next + // enter would result in error anyway + return r; + } + + if (!strcmp(key, "pgood")) + { + r = sd_bus_message_read(m, "v", "i", &newPgood); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "reading pgood" << std::endl; + return r; + } + r = tmgr->config.updatePropertyVal(key, std::to_string(newPgood)); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "processing pgood" << std::endl; + return r; + } + } + else + { + sd_bus_message_skip(m, "v"); + } + } + return 0; +} + +// Called by sd_event when Properties are changed in settingsd. +// Interested in changes to 'timeMode', 'timeOwner' and 'use_dhcp_ntp' +int TimeManager::processPropertyChange(sd_bus_message* m, void* userdata, + sd_bus_error* retError) +{ + auto tmgr = static_cast<TimeManager*>(userdata); + const char* key = nullptr; + const char* value = nullptr; + auto r = 0; + + std::cout <<" User Settings have changed.." << std::endl; + + // input data is "sa{sv}as" and we are just interested in a{sv} + r = sd_bus_message_skip(m, "s"); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) << + "skipping interface name in data" << std::endl; + goto finish; + } + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<"entering the dictionary" << std::endl; + goto finish; + } + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, + "sv")) > 0) + { + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<" reading the key from dict" << std::endl; + // Can not continue here since the next + // enter would result in error anyway + goto finish; + } + + if (!strcmp(key, "time_mode")) + { + r = sd_bus_message_read(m, "v", "s", &value); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "reading timeMode" << std::endl; + goto finish; + } + r = tmgr->config.updatePropertyVal(key, value); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "processing timeMode" << std::endl; + goto finish; + } + } + else if (!strcmp(key, "time_owner")) + { + r = sd_bus_message_read(m, "v", "s", &value); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "reading timeOwner" << std::endl; + goto finish; + } + r = tmgr->config.updatePropertyVal(key, value); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "processing time_owner" << std::endl; + goto finish; + } + else if (tmgr->config.isSplitModeChanged()) + { + // Must have been a change away from mode SPLIT + tmgr->resetHostOffset(); + } + } + else if (!strcmp(key, "use_dhcp_ntp")) + { + r = sd_bus_message_read(m, "v", "s", &value); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<"reading use_dhcp_ntp" << std::endl; + goto finish; + } + r = tmgr->config.updatePropertyVal(key, value); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "processing dhcp_ntp" << std::endl; + goto finish; + } + } + else + { + sd_bus_message_skip(m, "v"); + } + } +finish: + return r; +} + +// Sets up callback handlers for activities on : +// 1) user request on SD_BUS +// 2) Time change +// 3) Settings change +// 4) System state change; +int TimeManager::registerCallbackHandlers() +{ + constexpr auto WATCH_SETTING_CHANGE = + "type='signal',interface='org.freedesktop.DBus.Properties'," + "path='/org/openbmc/settings/host0',member='PropertiesChanged'"; + + constexpr auto WATCH_PGOOD_CHANGE = + "type='signal',interface='org.freedesktop.DBus.Properties'," + "path='/org/openbmc/control/power0',member='PropertiesChanged'"; + + // Extract the descriptor out of sd_bus construct. + auto sdBusFd = sd_bus_get_fd(iv_TimeBus); + + auto r = sd_event_add_io(iv_Event, &iv_EventSource, sdBusFd, EPOLLIN, + processSdBusMessage, this); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<" adding sd_bus_message handler" << std::endl; + return r; + } + + // Choose the MAX time that is possible to aviod mis fires. + itimerspec maxTime {}; + maxTime.it_value.tv_sec = TIME_T_MAX; + + auto timeFd = timerfd_create(CLOCK_REALTIME, 0); + if (timeFd < 0) + { + std::cerr << "Errorno: " << errno << " creating timerfd" << std::endl; + return -1; + } + + r = timerfd_settime(timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, + &maxTime, nullptr); + if (r) + { + std::cerr << "Errorno: " << errno << "Setting timerfd" << std::endl; + return -1; + } + + // Wake me up *only* if someone SETS the time + r = sd_event_add_io(iv_Event, &iv_EventSource, timeFd, EPOLLIN, + processTimeChange, this); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "adding time_change handler" << std::endl; + return r; + } + + // Watch for property changes in settingsd + r = sd_bus_add_match(iv_TimeBus, NULL, WATCH_SETTING_CHANGE, + processPropertyChange, this); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<" adding property change listener" << std::endl; + return r; + } + + // Watch for state change. Only reliable one to count on is + // state of [pgood]. value of [1] meaning host is powering on / powered + // on. [0] means powered off. + r = sd_bus_add_match(iv_TimeBus, NULL, WATCH_PGOOD_CHANGE, + processPgoodChange, this); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << " adding pgood change listener" << std::endl; + } + return r; +} + +int TimeManager::setupTimeManager() +{ + auto r = sd_bus_default_system(&iv_TimeBus); + if (r < 0) + { + std::cerr << "Error" << strerror(-r) + <<" connecting to system bus" << std::endl; + goto finish; + } + + r = sd_bus_add_object_manager(iv_TimeBus, NULL, cv_ObjPath); + if (r < 0) + { + std::cerr << "Error" << strerror(-r) + <<" adding object manager" << std::endl; + goto finish; + } + + std::cout <<"Registering dbus methods" << std::endl; + r = sd_bus_add_object_vtable(iv_TimeBus, + NULL, + cv_ObjPath, + cv_BusName, + timeServicesVtable, + this); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<" adding timer services vtable" << std::endl; + goto finish; + } + + // create a sd_event object and add handlers + r = sd_event_default(&iv_Event); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + <<"creating an sd_event" << std::endl; + goto finish; + } + + // Handlers called by sd_event when an activity + // is observed in event loop + r = registerCallbackHandlers(); + if (r < 0) + { + std::cerr << "Error setting up callback handlers" << std::endl; + goto finish; + } + + // Need to do this here since TimeConfig may update the necessary owners + r = config.processInitialSettings(iv_TimeBus); + if (r < 0) + { + std::cerr << "Error setting up configuration params" << std::endl; + goto finish; + } + + // Read saved values from previous run + r = readPersistentData(); + if (r < 0) + { + std::cerr << "Error reading persistent data" << std::endl; + goto finish; + } + + // Claim the bus + r = sd_bus_request_name(iv_TimeBus, cv_BusName, 0); + if (r < 0) + { + std::cerr << "Error: " << strerror(-r) + << "acquiring service name" << std::endl; + goto finish; + } +finish: + if (r < 0) + { + iv_EventSource = sd_event_source_unref(iv_EventSource); + iv_Event = sd_event_unref(iv_Event); + } + return r; +} + +int TimeManager::readPersistentData() +{ + using namespace std::chrono; + + // Get current host_offset + // When we reach here, TimeConfig would have been populated and would have + // applied the owner to SPLIT *if* the system allowed it. So check if we + // moved away from SPLIT and if so, make offset:0 + if (config.isSplitModeChanged()) + { + iv_HostOffset = microseconds(0); + auto r = config.writeData<decltype(iv_HostOffset.count())> + (cv_HostOffsetFile, + iv_HostOffset.count()); + if (r < 0) + { + std::cerr <<" Error saving offset to file" << std::endl; + return r; + } + } + else + { + auto hostTimeOffset = config.readData<long long int>(cv_HostOffsetFile); + iv_HostOffset = microseconds(hostTimeOffset); + std::cout <<"Last known host_offset:" << hostTimeOffset << std::endl; + } + + //How long was the FSP up prior to 'this' start + iv_UptimeUsec = duration_cast<microseconds> + (system_clock::now().time_since_epoch() - + steady_clock::now().time_since_epoch()); + if (iv_UptimeUsec.count() < 0) + { + std::cerr <<"Error reading uptime" << std::endl; + return -1; + } + std::cout <<"Initial Uptime Usec: " + << iv_UptimeUsec.count() << std::endl; + return 0; +} + +// Forever loop +int TimeManager::waitForClientRequest() +{ + return sd_event_loop(iv_Event); +} + +int main(int argc, char* argv[]) +{ + auto tmgr = std::make_unique<TimeManager>(); + + // Wait for the work + auto r = tmgr->waitForClientRequest(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/time-manager.hpp b/time-manager.hpp new file mode 100644 index 0000000..5e32a72 --- /dev/null +++ b/time-manager.hpp @@ -0,0 +1,303 @@ +#include <systemd/sd-bus.h> +#include <fstream> +#include <string> +#include <chrono> +#include "time-config.hpp" + +/** @class Time + * @brief Base class catering to common data needed by implementations + */ +class Time +{ +public: + /** @brief Takes time in microseconds and uses systemd/timedated + * to set the system time + * + * @param[in] timeOfDayUsec - Time value in microseconds + * @return - Status of time set operation + */ + int setTimeOfDay(const std::chrono::microseconds& timeOfDayUsec); + + /** @brief Reads BMC time + * + * @param[in] - None + * @return - time read in microseconds + */ + std::chrono::microseconds getBaseTime(); + + /** @brief Converts microseconds to human readable time value + * + * @param[in] timeInUsec - Time value in microseconds + * @return - Time converted to human readable format + */ + std::string convertToStr(const std::chrono::microseconds& timeInUsec); + + /** @brief Reference to config information due to need of policy data */ + const TimeConfig& config; + + /** Needed to access configuration variables */ + Time(const TimeConfig&); + +private: + Time(); +}; + +/** @class BmcTime + * @brief Provides time Set and time Get operations for BMC target + */ +class BmcTime: public Time +{ +public: + /** @brief Called when the time is to be read on BMC + * + * @param[in] m - sd_bus message. + * @param[out] retError - Error reporting structure + * @return - On no error, time read which is specified in + * microseonds and also in human readable format. + * + * - On error, retError populated + */ + int getTime(sd_bus_message* m, sd_bus_error* retError); + + /** @brief Called when the time is to be set on BMC + * + * @param[in] m - sd_bus message encapsulating time string + * @param[out] retError - Error reporting structure + * @return - On no error, 0, -1 otherwise or retError thrown + */ + int setTime(sd_bus_message* m, sd_bus_error* retError); + + /** @brief constructor */ + BmcTime(const TimeConfig&); + +private: + BmcTime(); +}; + +/** @class HostTime + * @brief Provides time Set and time Get operations for BMC target + */ +class HostTime: public Time +{ +public: + /** @brief Called when IPMI_GET_SEL_TIME is called + * + * @param[in] m - sd_bus message. + * @param[out] retError - Error reporting structure + * @return - On no error, time read which is specified in + * microseonds and also in human readable format. + * + * - On error, retError populated + */ + int getTime(sd_bus_message*, sd_bus_error*); + + /** @brief Called when the IPMI_SET_SEL_TIME is called + * + * @param[in] m - sd_bus message encapsulating time in seconds + * @param[out] retError - Error reporting structure + * @return - On no error, 0, -1 otherwise or retError thrown + */ + int setTime(sd_bus_message*, sd_bus_error*); + + /** @brief constructor */ + HostTime(const TimeConfig&, const std::chrono::microseconds&); + + /** @brief When the owner is SPLIT, the delta between HOST's time and BMC's + * time needs to be saved and this function returns current delta. + * + * @param[in] - None + * @return - offset in microseconds + */ + inline std::chrono::microseconds getChangedOffset() const + { + return changedOffset; + } + + /** @brief Reference to host's offset in case of SPLIT owner */ + const std::chrono::microseconds& iv_Offset; + +private: + HostTime(); + + /** @brief The delta offset of Host and BMC time */ + std::chrono::microseconds changedOffset; +}; + +/** @class TimeManager + * @brief Caters to client requests with Set and Get time and configuration + * changes + */ +class TimeManager +{ +public: + // Do not have a usecase of copying this object so disable + TimeManager(); + ~TimeManager() = default; + TimeManager(const TimeManager&) = delete; + TimeManager& operator=(const TimeManager&) = delete; + TimeManager(TimeManager&&) = delete; + TimeManager& operator=(TimeManager&&) = delete; + + // Maintains *all* the config that is needed for TimeManager. + TimeConfig config; + + /** @brief Callback handlers invoked by dbus on GetTime client requests + * + * @param[in] m - sd_bus message encapsulating the time target + * @param[in] userdata - context that is filled while registering this + * @param[out] retError - Error reporting mechanism + * + * @return - On no error, time in microseconds and human + * readable string. retError otherwise. + */ + int getTime(sd_bus_message* m, void* userdata, sd_bus_error* retError); + + /** @brief Callback handlers invoked by dbus on SetTime client requests + * + * @param[in] m - sd_bus message encapsulating the time target and + * time value either in string or in seconds. + * @param[in] userdata - client context that is filled while registering + * @param[out] retError - Error reporting mechanism + * + * @return - On no error, time in microseconds and human + * readable string. retError otherwise. + */ + int setTime(sd_bus_message* m, void* userdata, sd_bus_error* retError); + + /** @brief sd_event callback handlers on the requests coming in dbus + * These are actually GetTime and SetTime requests + * + * @param[in] es - Event Structure + * @param[in] fd - file descriptor that had 'read' activity + * @param[in] revents - generic linux style return event + * @param[in] userdata - Client context filled while registering + * + * @return - 0 for success, failure otherwise. + */ + static int processSdBusMessage(sd_event_source* es, int fd, + uint32_t revents, void* userdata); + + /** @brief sd_event callback handler called whenever there is a + * time change event indicated by timerfd expiring. This happens + * whenever the time is set on BMC by any source. + * + * @param[in] es - Event Structure + * @param[in] fd - file descriptor that had 'read' activity + * @param[in] revents - generic linux style return event + * @param[in] userdata - Client context filled while registering + * + * @return - 0 for success, failure otherwise. + */ + static int processTimeChange(sd_event_source* es, int fd, + uint32_t revents, void* userdata); + + /** @brief sd_event callback handler called whenever a settings + * property is changed. + * This gets called into whenever "time_mode", "time_owner", + * "use_dhcp_ntp" properties are changed + * + * @param[in] es - Event Structure + * @param[in] fd - file descriptor that had 'read' activity + * @param[in] revents - generic linux style return event + * @param[in] userdata - Client context filled while registering + * + * @return - 0 for success, failure otherwise. + */ + static int processPropertyChange(sd_bus_message*, + void*,sd_bus_error*); + + /** @brief sd_event callback handler called whenever Pgood property is + * changed + * + * @param[in] es - Event Structure + * @param[in] fd - file descriptor that had 'read' activity + * @param[in] revents - generic linux style return event + * @param[in] userdata - Client context filled while registering + * + * @return - 0 for success, failure otherwise. + */ + static int processPgoodChange(sd_bus_message*, + void*,sd_bus_error*); + + /** @brief registers callsback handlers for sd_event loop + * + * @param[in] - None + * @return - 0 if everything goes well, -1 otherwise + */ + int registerCallbackHandlers(); + + /** @brief Makes the Delta between Host and BMC time as 'ZERO'. This + * essentially only means that time owner was SPLIT before + * and now changed to something else. + * + * @param[in] - None + * @return - 0 if everything goes well, -1 otherwise. + */ + int resetHostOffset(); + + /** @brief Reads what was the last delta offset stored in file + * + * @param[in] - None + * @return - 0 if everything goes well, -1 otherwise. + */ + int readPersistentData(); + + /** @brief waits on sd_events loop for client requests + * + * @param[in] - None + * @return - 0 if everything goes well, -1 otherwise. + */ + int waitForClientRequest(); + + inline auto getHostOffset() const + { + return iv_HostOffset; + } + + inline auto updateHostOffset(const std::chrono::microseconds& newOffset) + { + iv_HostOffset = newOffset; + } + + inline auto getUptimeUsec() const + { + return iv_UptimeUsec; + } + + inline auto updateUptimeUsec(const std::chrono::microseconds& newUpTime) + { + iv_UptimeUsec = newUpTime; + } + + inline sd_bus* getTimeBus() const + { + return iv_TimeBus; + } + +private: + // What was the last known host offset. + std::chrono::microseconds iv_HostOffset; + + // How long was the BMC up for prior to this boot + std::chrono::microseconds iv_UptimeUsec; + + // Used for registering sd_bus callback handlers. + sd_event_source* iv_EventSource; + sd_event* iv_Event; + sd_bus* iv_TimeBus; + + // Dbus communication enablers. + static constexpr auto cv_BusName = "org.openbmc.TimeManager"; + static constexpr auto cv_ObjPath = "/org/openbmc/TimeManager"; + static constexpr auto cv_IntfName = "org.openbmc.TimeManager"; + + // Store the offset in File System. Read back when TimeManager starts. + static constexpr auto cv_HostOffsetFile = "/var/lib/obmc/saved_host_offset"; + + /** @brief Sets up internal data structures and callback handler at startup + * + * @param[in] - None + * @return - 0 if everything goes well, -1 otherwise + */ + int setupTimeManager(void); +}; diff --git a/time-register.c b/time-register.c new file mode 100644 index 0000000..cdc1dd2 --- /dev/null +++ b/time-register.c @@ -0,0 +1,25 @@ +#include <systemd/sd-bus.h> +#include "time-register.hpp" +#include <sys/timerfd.h> +#include <errno.h> +#include <stdio.h> + +/* Function pointer of APIs exposed via Dbus */ +//const sd_bus_vtable timeServicesVtable[] = +const sd_bus_vtable timeServicesVtable [] = +{ + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("curr_time_mode", "s", getCurrTimeModeProperty, 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("curr_time_owner", "s", getCurrTimeOwnerProperty, 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("requested_time_mode", "s", getReqTimeModeProperty, 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("requested_time_owner", "s", getReqTimeOwnerProperty, 0, + SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_METHOD("GetTime", "s", "sx", &GetTime, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetTime", "ss", "i", &SetTime, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; diff --git a/time-register.hpp b/time-register.hpp new file mode 100644 index 0000000..bf776d3 --- /dev/null +++ b/time-register.hpp @@ -0,0 +1,17 @@ +#include <systemd/sd-bus.h> +#ifdef __cplusplus +extern "C" { +#endif +int getCurrTimeModeProperty(sd_bus*, const char*, const char*, const char*, + sd_bus_message*, void*, sd_bus_error*); +int getCurrTimeOwnerProperty(sd_bus*, const char*, const char*, const char*, + sd_bus_message*, void*, sd_bus_error*); +int getReqTimeModeProperty(sd_bus*, const char*, const char*, const char*, + sd_bus_message*, void*, sd_bus_error*); +int getReqTimeOwnerProperty(sd_bus*, const char*, const char*, const char*, + sd_bus_message*, void*, sd_bus_error*); +int GetTime(sd_bus_message*, void*, sd_bus_error*); +int SetTime(sd_bus_message*, void*, sd_bus_error*); +#ifdef __cplusplus +}; +#endif |