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