Implement HostEpoch set time logic

1. When setting host epoch, follow below logic:

        Mode  | Owner | Set Host Time
        ----- | ----- | -------------
        NTP   | BMC   | Not allowed
        NTP   | HOST  | Not allowed
        NTP   | SPLIT | OK, and just save offset
        NTP   | BOTH  | Not allowed
        MANUAL| BMC   | Not allowed
        MANUAL| HOST  | OK, and set time to BMC
        MANUAL| SPLIT | OK, and just save offset
        MANUAL| BOTH  | OK, and set time to BMC

2. If owner is SPLIT and BMC time is changed, update the offset accordinly;
3. Use timerfd to get notified on BMC time change, and update host time
diff accordingly;
4. Add unit test cases.

Change-Id: I2d60a821f7da9b689c579ae7ab672cc37967322c
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/bmc_epoch.cpp b/bmc_epoch.cpp
index ed482ad..7bc6c22 100644
--- a/bmc_epoch.cpp
+++ b/bmc_epoch.cpp
@@ -1,6 +1,22 @@
 #include "bmc_epoch.hpp"
 
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+
+// 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 even though we set CANCEL_ON_SET
+#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
 
 namespace phosphor
 {
@@ -11,9 +27,61 @@
 
 BmcEpoch::BmcEpoch(sdbusplus::bus::bus& bus,
                    const char* objPath)
-    : EpochBase(bus, objPath)
+    : EpochBase(bus, objPath),
+      bus(bus)
 {
-    // Empty
+    initialize();
+}
+
+void BmcEpoch::initialize()
+{
+    using InternalFailure = sdbusplus::xyz::openbmc_project::Common::
+                                Error::InternalFailure;
+
+    // Subscribe time change event
+    // Choose the MAX time that is possible to avoid mis fires.
+    constexpr itimerspec maxTime = {
+        {0, 0}, // it_interval
+        {TIME_T_MAX, 0}, //it_value
+    };
+
+    timeFd = timerfd_create(CLOCK_REALTIME, 0);
+    if (timeFd == -1)
+    {
+        log<level::ERR>("Failed to create timerfd",
+                        entry("ERRNO=%d", errno),
+                        entry("ERR=%s", strerror(errno)));
+        elog<InternalFailure>();
+    }
+
+    auto r = timerfd_settime(timeFd,
+                             TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
+                             &maxTime,
+                             nullptr);
+    if (r != 0)
+    {
+        log<level::ERR>("Failed to set timerfd",
+                        entry("ERRNO=%d", errno),
+                        entry("ERR=%s", strerror(errno)));
+        elog<InternalFailure>();
+    }
+
+    sd_event_source* es;
+    r = sd_event_add_io(bus.get_event(), &es,
+                        timeFd, EPOLLIN, onTimeChange, this);
+    if (r < 0)
+    {
+        log<level::ERR>("Failed to add event",
+                        entry("ERRNO=%d", -r),
+                        entry("ERR=%s", strerror(-r)));
+        elog<InternalFailure>();
+    }
+    timeChangeEventSource.reset(es);
+}
+
+BmcEpoch::~BmcEpoch()
+{
+    close(timeFd);
 }
 
 uint64_t BmcEpoch::elapsed() const
@@ -24,6 +92,18 @@
 
 uint64_t BmcEpoch::elapsed(uint64_t value)
 {
+    /*
+        Mode  | Owner | Set BMC Time
+        ----- | ----- | -------------
+        NTP   | BMC   | Not allowed
+        NTP   | HOST  | Not allowed
+        NTP   | SPLIT | Not allowed
+        NTP   | BOTH  | Not allowed
+        MANUAL| BMC   | OK
+        MANUAL| HOST  | Not allowed
+        MANUAL| SPLIT | OK
+        MANUAL| BOTH  | OK
+    */
     if (timeMode == Mode::NTP)
     {
         log<level::ERR>("Setting BmcTime with NTP mode is not allowed");
@@ -37,13 +117,46 @@
         return 0;
     }
 
-    auto time = std::chrono::microseconds(value);
+    auto time = microseconds(value);
     setTime(time);
 
+    notifyBmcTimeChange(time);
+
     server::EpochTime::elapsed(value);
     return value;
 }
 
+void BmcEpoch::setBmcTimeChangeListener(BmcTimeChangeListener* listener)
+{
+    timeChangeListener = listener;
+}
+
+void BmcEpoch::notifyBmcTimeChange(const microseconds& time)
+{
+    // Notify listener if it exists
+    if (timeChangeListener)
+    {
+        timeChangeListener->onBmcTimeChanged(time);
+    }
+}
+
+int BmcEpoch::onTimeChange(sd_event_source* es, int fd,
+                           uint32_t /* revents */, void* userdata)
+{
+    auto bmcEpoch = static_cast<BmcEpoch*>(userdata);
+
+    std::array<char, 64> time {};
+
+    // We are not interested in the data here.
+    // So read until there is no new data here in the FD
+    while (read(fd, time.data(), time.max_size()) > 0);
+
+    log<level::INFO>("BMC system time is changed");
+    bmcEpoch->notifyBmcTimeChange(bmcEpoch->getTime());
+
+    return 0;
+}
+
 } // namespace time
 } // namespace phosphor