Intialize new phosphor-time-manager
phosphor-time-manager will be refactored to use sdbusplus interfaces.
This is a initial commit that EpochBase is implemented based on dbus
interface xyz/openbmc_project/Time/EpochTime.interface.yaml.
EpochBase is the base class that wraps EpochTime interface, and is
initialized with time mode and owner from settingsd.
An initial unit test case is added.
Change-Id: Ic944b70f63ec3c0329762cc8874f0f57b09ddce3
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/Makefile.am b/Makefile.am
index b25fa12..4b4cf02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,17 +1,28 @@
AM_DEFAULT_SOURCE_EXT = .cpp
-sbin_PROGRAMS = \
- timemanager
+sbin_PROGRAMS = phosphor-timemanager
-timemanager_SOURCES = \
- time-register.c \
- time-config.cpp \
- time-manager.cpp \
- settings.cpp
+noinst_LTLIBRARIES = libtimemanager.la
-timemanager_LDFLAGS = \
- $(SYSTEMD_LIBS) \
- $(libmapper_LIBS) \
- $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
- $(SDBUSPLUS_LIBS) \
- $(PHOSPHOR_LOGGING_LIBS)
+libtimemanager_la_SOURCES = \
+ epoch_base.cpp
+
+phosphor_timemanager_SOURCES = \
+ main.cpp
+
+phosphor_timemanager_LDADD = libtimemanager.la
+
+generic_cxx_flags = $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(SDBUSPLUS_CFLAGS)
+
+generic_ld_flags = $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(SDBUSPLUS_LIBS)
+
+libtimemanager_la_CXXFLAGS = $(generic_cxx_flags)
+libtimemanager_la_LIBADD = $(generic_ld_flags)
+
+phosphor_timemanager_CXXFLAGS = $(generic_cxx_flags)
+
+phosphor_timemanager_LDFLAGS = $(generic_ld_flags)
+
+SUBDIRS = . test
diff --git a/configure.ac b/configure.ac
index fc0da82..03f8347 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,43 +1,34 @@
# Initialization
AC_PREREQ([2.69])
AC_INIT([phosphor-time-manager], [1.0], [https://github.com/openbmc/phosphor-time-manager/issues])
+AC_LANG([C++])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
AM_SILENT_RULES([yes])
-# Checks for programs.
+# 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], ,[AC_MSG_ERROR([Could not find libmapper...openbmc/phosphor-objmgr package required])])
-PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221])
+# Surpress the --with-libtool-sysroot error
+LT_INIT
+
+# Check for libraries
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221],,\
+ AC_MSG_ERROR(["Systemd required and not found"]))
PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],,\
AC_MSG_ERROR(["Requires phosphor-dbus-interfaces package."]))
-PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,
+PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,\
AC_MSG_ERROR(["Requires sdbusplus package."]))
PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],,\
AC_MSG_ERROR(["Requires phosphor-logging package."]))
-# 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.
-AX_CXX_COMPILE_STDCXX_14([noext])
-AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CFLAGS])
-AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
-
-# Checks for library functions.
-LT_INIT # Removes 'unrecognized options: --with-libtool-sysroot'
-
+# gtest
# Check/set gtest specific functions.
AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
AC_SUBST(GTEST_CPPFLAGS)
+# Test cases require SDK so only build if we're told to (and SDK is available)
AC_ARG_ENABLE([oe-sdk],
AS_HELP_STRING([--enable-oe-sdk], [Link testcases absolutely against OE SDK so they can be ran within it.])
)
@@ -56,6 +47,23 @@
AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags])
)
-# Create configured output
-AC_CONFIG_FILES([Makefile])
+# Checks for typedefs, structures, and compiler characteristics.
+AX_CXX_COMPILE_STDCXX_14([noext])
+AX_APPEND_COMPILE_FLAGS([-fpic -Wall -Werror], [CXXFLAGS])
+
+# DBUS interface
+AC_ARG_VAR(BUSNAME, [The Time Manager Dbus busname to own])
+AS_IF([test "x$BUSNAME" == "x"], [BUSNAME="xyz.openbmc_project.Time.Manager"])
+AC_DEFINE_UNQUOTED([BUSNAME], ["$BUSNAME"], [The Time Manager DBus busname to own])
+
+AC_ARG_VAR(OBJPATH_BMC, [The bmc epoch Dbus root])
+AS_IF([test "x$OBJPATH_BMC" == "x"], [OBJPATH_BMC="/xyz/openbmc_project/time/bmc"])
+AC_DEFINE_UNQUOTED([OBJPATH_BMC], ["$OBJPATH_BMC"], [The bmc epoch Dbus root])
+
+AC_ARG_VAR(OBJPATH_HOST, [The host epoch Dbus root])
+AS_IF([test "x$OBJPATH_HOST" == "x"], [OBJPATH_HOST="/xyz/openbmc_project/time/host"])
+AC_DEFINE_UNQUOTED([OBJPATH_HOST], ["$OBJPATH_HOST"], [The host epoch Dbus root])
+
+
+AC_CONFIG_FILES([Makefile test/Makefile])
AC_OUTPUT
diff --git a/epoch_base.cpp b/epoch_base.cpp
new file mode 100644
index 0000000..06232bb
--- /dev/null
+++ b/epoch_base.cpp
@@ -0,0 +1,131 @@
+#include "epoch_base.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+#include <iomanip>
+#include <sstream>
+
+namespace // anonymous
+{
+constexpr auto SETTINGS_SERVICE = "org.openbmc.settings.Host";
+constexpr auto SETTINGS_PATH = "/org/openbmc/settings/host0";
+constexpr auto SETTINGS_INTERFACE = "org.openbmc.settings.Host";
+constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
+constexpr auto METHOD_GET = "Get";
+}
+
+namespace phosphor
+{
+namespace time
+{
+
+using namespace phosphor::logging;
+
+const std::map<std::string, EpochBase::Owner>
+EpochBase::ownerMap = {
+ { "BMC", EpochBase::Owner::BMC },
+ { "HOST", EpochBase::Owner::HOST },
+ { "SPLIT", EpochBase::Owner::SPLIT },
+ { "BOTH", EpochBase::Owner::BOTH },
+};
+
+EpochBase::EpochBase(sdbusplus::bus::bus& bus,
+ const char* objPath)
+ : sdbusplus::server::object::object<EpochTime>(bus, objPath, true),
+ bus(bus)
+{
+ initialize();
+ // Deferred this until we could get our property correct
+ emit_object_added();
+}
+
+void EpochBase::setCurrentTimeMode(const std::string& mode)
+{
+ log<level::INFO>("Time mode is changed",
+ entry("MODE=%s", mode.c_str()));
+ timeMode = convertToMode(mode);
+}
+
+void EpochBase::setCurrentTimeOwner(const std::string& owner)
+{
+ log<level::INFO>("Time owner is changed",
+ entry("OWNER=%s", owner.c_str()));
+ timeOwner = convertToOwner(owner);
+}
+
+void EpochBase::initialize()
+{
+ setCurrentTimeMode(getSettings("time_mode"));
+ setCurrentTimeOwner(getSettings("time_owner"));
+ // TODO: subscribe settingsd's property changes callback
+}
+
+std::string EpochBase::getSettings(const char* value) const
+{
+ sdbusplus::message::variant<std::string> mode;
+ auto method = bus.new_method_call(SETTINGS_SERVICE,
+ SETTINGS_PATH,
+ PROPERTY_INTERFACE,
+ METHOD_GET);
+ method.append(SETTINGS_INTERFACE, value);
+ auto reply = bus.call(method);
+ if (reply)
+ {
+ reply.read(mode);
+ }
+
+ return mode.get<std::string>();
+}
+
+EpochBase::Mode EpochBase::convertToMode(const std::string& mode)
+{
+ if (mode == "NTP")
+ {
+ return Mode::NTP;
+ }
+ else if (mode == "MANUAL")
+ {
+ return Mode::MANUAL;
+ }
+ else
+ {
+ log<level::ERR>("Unrecognized mode",
+ entry("%s", mode.c_str()));
+ return Mode::NTP;
+ }
+}
+
+EpochBase::Owner EpochBase::convertToOwner(const std::string& owner)
+{
+ auto it = ownerMap.find(owner);
+ if (it == ownerMap.end())
+ {
+ log<level::ERR>("Unrecognized owner",
+ entry("%s", owner.c_str()));
+ return Owner::BMC;
+ }
+ return it->second;
+}
+
+using namespace std::chrono;
+void EpochBase::setTime(const microseconds& usec)
+{
+ auto method = bus.new_method_call("org.freedesktop.timedate1",
+ "/org/freedesktop/timedate1",
+ "org.freedesktop.timedate1",
+ "SetTime");
+ method.append(static_cast<int64_t>(usec.count()),
+ false, // relative
+ false); // user_interaction
+ bus.call_noreply(method);
+}
+
+microseconds EpochBase::getTime() const
+{
+ auto now = system_clock::now();
+ return duration_cast<microseconds>
+ (now.time_since_epoch());
+}
+
+} // namespace time
+} // namespace phosphor
diff --git a/epoch_base.hpp b/epoch_base.hpp
new file mode 100644
index 0000000..fc505d0
--- /dev/null
+++ b/epoch_base.hpp
@@ -0,0 +1,146 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Time/EpochTime/server.hpp>
+
+#include <chrono>
+
+namespace phosphor
+{
+namespace time
+{
+
+/** @class EpochBase
+ * @brief Base class for OpenBMC EpochTime implementation.
+ * @details A base class that implements xyz.openbmc_project.Time.EpochTime
+ * DBus API for epoch time.
+ */
+class EpochBase : public sdbusplus::server::object::object <
+ sdbusplus::xyz::openbmc_project::Time::server::EpochTime >
+{
+ public:
+ friend class TestEpochBase;
+
+ /** @brief Supported time modes
+ * NTP Time sourced by Network Time Server
+ * MANUAL User of the system need to set the time
+ */
+ enum class Mode
+ {
+ 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 Owner
+ {
+ BMC,
+ HOST,
+ SPLIT,
+ BOTH,
+ };
+
+ EpochBase(sdbusplus::bus::bus& bus,
+ const char* objPath);
+
+ protected:
+ /** @brief Persistent sdbusplus DBus connection */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief The current time mode */
+ Mode timeMode;
+
+ /** @brief The current time owner */
+ Owner timeOwner;
+
+ /** @brief Set current time to system
+ *
+ * This function set the time to system by invoking systemd
+ * org.freedesktop.timedate1's SetTime method.
+ *
+ * @param[in] timeOfDayUsec - Microseconds since UTC
+ */
+ void setTime(const std::chrono::microseconds& timeOfDayUsec);
+
+ /** @brief Get current time
+ *
+ * @return Microseconds since UTC
+ */
+ std::chrono::microseconds getTime() const;
+
+ /** @brief Convert a string to enum Mode
+ *
+ * Convert the time mode string to enum.
+ * Valid strings are "NTP", "MANUAL"
+ * If it's not a valid time mode string, return NTP.
+ *
+ * @param[in] mode - The string of time mode
+ *
+ * @return The Mode enum
+ */
+ static Mode convertToMode(const std::string& mode);
+
+ /** @brief Convert a string to enum Owner
+ *
+ * Convert the time owner string to enum.
+ * Valid strings are "BMC", "HOST", "SPLIT", "BOTH"
+ * If it's not a valid time owner string, return BMC.
+ *
+ * @param[in] owner - The string of time owner
+ *
+ * @return The Owner enum
+ */
+ static Owner convertToOwner(const std::string& owner);
+
+ private:
+ /** @brief Initialize the time mode and owner */
+ void initialize();
+
+ /** @brief Set current time mode
+ *
+ * @param[in] mode - The string of time mode
+ */
+ void setCurrentTimeMode(const std::string& mode);
+
+ /** @brief Set current time owner
+ *
+ * @param[in] owner - The string of time owner
+ */
+ void setCurrentTimeOwner(const std::string& owner);
+
+ /** @brief Get setting value from settings manager
+ *
+ * @param[in] setting - The string of the setting to get
+ *
+ * @return The value of the setting
+ */
+ std::string getSettings(const char* setting) const;
+
+ /** @brief The map maps the string key to enum Owner */
+ static const std::map<std::string, Owner> ownerMap;
+};
+
+} // namespace time
+} // namespace phosphor
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..0cf2807
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,17 @@
+#include <sdbusplus/bus.hpp>
+
+#include "config.h"
+
+int main()
+{
+ auto bus = sdbusplus::bus::new_default();
+
+ bus.request_name(BUSNAME);
+
+ while (true)
+ {
+ bus.process_discard();
+ bus.wait();
+ }
+ return 0;
+}
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..66e1ae5
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,26 @@
+AM_CPPFLAGS = -I${top_srcdir}
+check_PROGRAMS =
+
+TESTS = $(check_PROGRAMS)
+
+check_PROGRAMS += test
+
+test_SOURCES = \
+ TestEpochBase.cpp
+
+test_LDADD = $(top_builddir)/libtimemanager.la
+
+test_CPPFLAGS = $(GTEST_CPPFLAGS) \
+ $(AM_CPPFLAGS)
+
+test_CXXFLAGS = $(PTHREAD_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(SDBUSPLUS_CFLAGS)
+
+test_LDFLAGS = -lgmock_main \
+ -lgmock \
+ $(PTHREAD_LIBS) \
+ $(OESDK_TESTCASE_FLAGS)
+
+test_LDFLAGS += $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(SDBUSPLUS_LIBS)
diff --git a/test/TestEpochBase.cpp b/test/TestEpochBase.cpp
new file mode 100644
index 0000000..c3ca7e1
--- /dev/null
+++ b/test/TestEpochBase.cpp
@@ -0,0 +1,64 @@
+#include <sdbusplus/bus.hpp>
+#include <gtest/gtest.h>
+
+#include "epoch_base.hpp"
+
+namespace phosphor
+{
+namespace time
+{
+
+class TestEpochBase : public testing::Test
+{
+ public:
+ using Mode = EpochBase::Mode;
+ using Owner = EpochBase::Owner;
+
+ sdbusplus::bus::bus bus;
+ EpochBase epochBase;
+
+ TestEpochBase()
+ : bus(sdbusplus::bus::new_default()),
+ epochBase(bus, "")
+ {
+ // Empty
+ }
+
+ // Proxies for EpochBase's private members and functions
+ Mode convertToMode(const std::string& mode)
+ {
+ return EpochBase::convertToMode(mode);
+ }
+ Owner convertToOwner(const std::string& owner)
+ {
+ return EpochBase::convertToOwner(owner);
+ }
+};
+
+TEST_F(TestEpochBase, convertToMode)
+{
+ EXPECT_EQ(Mode::NTP, convertToMode("NTP"));
+ EXPECT_EQ(Mode::MANUAL, convertToMode("MANUAL"));
+
+ // All unrecognized strings are mapped to Ntp
+ EXPECT_EQ(Mode::NTP, convertToMode(""));
+ EXPECT_EQ(Mode::NTP, convertToMode("Manual"));
+ EXPECT_EQ(Mode::NTP, convertToMode("whatever"));
+}
+
+
+TEST_F(TestEpochBase, convertToOwner)
+{
+ EXPECT_EQ(Owner::BMC, convertToOwner("BMC"));
+ EXPECT_EQ(Owner::HOST, convertToOwner("HOST"));
+ EXPECT_EQ(Owner::SPLIT, convertToOwner("SPLIT"));
+ EXPECT_EQ(Owner::BOTH, convertToOwner("BOTH"));
+
+ // All unrecognized strings are mapped to Bmc
+ EXPECT_EQ(Owner::BMC, convertToOwner(""));
+ EXPECT_EQ(Owner::BMC, convertToOwner("Split"));
+ EXPECT_EQ(Owner::BMC, convertToOwner("xyz"));
+}
+
+}
+}