sched-host-tran: handle with BMC time changing
Handle with different processes when BMC time is changed after scheduled time
is set.
Tested:
Case1: BMC time is changed to be later than current time but still earlier than
scheduled time
1. Get current time
# date
Tue Feb 25 07:07:44 UTC 2020
# date +%s
1582614271
2. Schedule time, do host transition after at 07:20:00 around
# busctl get-property xyz.openbmc_project.State.ScheduledHostTransition \
/xyz/openbmc_project/state/host0 \
xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime
t 1582615256
3. Change BMC time to 07:19:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582615136000000
# date
Tue Feb 25 07:19:20 UTC 2020
# date +%s
1582615187
4. Host transition is done after 1 minute around,
instead of waiting 13 mins around.
Case2: BMC time is changed after scheduled time is reached
Following Case1, the scheduled time is reached already,
1. Change BMC time to 07:10:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582614600000000
2. APP shows "The function Scheduled Host Transition is disabled", because
the scheduled time is reached already and the scheduled time has been set
to 0 after host transition is triggered.
Case3: BMC time is changed to be bigger than scheduled time before scheduled
time is reached
1. Set scheduled time 07:08:00 around
# busctl set-property xyz.openbmc_project.State.ScheduledHostTransition \
/xyz/openbmc_project/state/host0 \
xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime t 1582787314
2. Change BMC time to 07:10:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582787434000000
3. It will do host transition as requested.
Case4: BMC time is changed to be earlier than current time
1. Set scheduled time 07:10:00 around
# busctl set-property xyz.openbmc_project.State.ScheduledHostTransition \
/xyz/openbmc_project/state/host0 \
xyz.openbmc_project.State.ScheduledHostTransition ScheduledTime t 1582787434
2. Change BMC time to 07:08:00 around
# busctl set-property xyz.openbmc_project.Time.Manager \
/xyz/openbmc_project/time/bmc xyz.openbmc_project.Time.EpochTime Elapsed \
t 1582787314000000
3. App will wait 2 minutes more to do host transition.
Change-Id: I23228be944d1b2f71161317228c8b16d7f5ca4eb
Signed-off-by: Carol Wang <wangkair@cn.ibm.com>
diff --git a/scheduled_host_transition.cpp b/scheduled_host_transition.cpp
index b65d097..9fdd32b 100644
--- a/scheduled_host_transition.cpp
+++ b/scheduled_host_transition.cpp
@@ -5,6 +5,17 @@
#include <phosphor-logging/log.hpp>
#include <xyz/openbmc_project/ScheduledTime/error.hpp>
#include <chrono>
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+// Need 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
{
@@ -39,7 +50,8 @@
timer.setEnabled(false);
}
- log<level::INFO>("The function Scheduled Host Transition is disabled.");
+ log<level::INFO>("scheduledTime: The function Scheduled Host "
+ "Transition is disabled.");
}
else
{
@@ -141,6 +153,114 @@
HostTransition::scheduledTime(0);
}
+void ScheduledHostTransition::initialize()
+{
+ // 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
+ };
+
+ // Create and operate on a timer that delivers timer expiration
+ // notifications via a file descriptor.
+ timeFd = timerfd_create(CLOCK_REALTIME, 0);
+ if (timeFd == -1)
+ {
+ auto eno = errno;
+ log<level::ERR>("Failed to create timerfd", entry("ERRNO=%d", eno),
+ entry("RC=%d", timeFd));
+ throw std::system_error(eno, std::system_category());
+ }
+
+ // Starts the timer referred to by the file descriptor fd.
+ // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
+ // and the clock for this timer is CLOCK_REALTIME, then mark this timer
+ // as cancelable if the real-time clock undergoes a discontinuous change.
+ // In this way, we can monitor whether BMC time is changed or not.
+ auto r = timerfd_settime(
+ timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
+ if (r != 0)
+ {
+ auto eno = errno;
+ log<level::ERR>("Failed to set timerfd", entry("ERRNO=%d", eno),
+ entry("RC=%d", r));
+ throw std::system_error(eno, std::system_category());
+ }
+
+ sd_event_source* es;
+ // Add a new I/O event source to an event loop. onTimeChange will be called
+ // when the event source is triggered.
+ r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
+ if (r < 0)
+ {
+ auto eno = errno;
+ log<level::ERR>("Failed to add event", entry("ERRNO=%d", eno),
+ entry("RC=%d", r));
+ throw std::system_error(eno, std::system_category());
+ }
+ timeChangeEventSource.reset(es);
+}
+
+ScheduledHostTransition::~ScheduledHostTransition()
+{
+ close(timeFd);
+}
+
+void ScheduledHostTransition::handleTimeUpdates()
+{
+ if (!timer.isEnabled())
+ {
+ return;
+ }
+ // Stop the timer if it's running
+ timer.setEnabled(false);
+
+ // Get scheduled time
+ auto schedTime = HostTransition::scheduledTime();
+
+ if (schedTime == 0)
+ {
+ log<level::INFO>("handleTimeUpdates: The function Scheduled Host "
+ "Transition is disabled.");
+ return;
+ }
+
+ auto deltaTime = seconds(schedTime) - getTime();
+ if (deltaTime <= seconds(0))
+ {
+ // When BMC time is changed to be later than scheduled time, check the
+ // state of host transition to decide whether need to do host transition
+ hostTransition();
+ // Set scheduledTime to 0 to disable host transition
+ HostTransition::scheduledTime(0);
+ }
+ else
+ {
+ // Start a timer to do host transition at scheduled time
+ timer.restart(deltaTime);
+ }
+}
+
+int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
+ uint32_t /* revents */,
+ void* userdata)
+{
+ auto schedHostTran = static_cast<ScheduledHostTransition*>(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");
+ schedHostTran->handleTimeUpdates();
+
+ return 0;
+}
+
} // namespace manager
} // namespace state
} // namespace phosphor