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>
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;
+}