blob: 69cbcb2a38f72592c12ef4adaa24bf35edf185e3 [file] [log] [blame]
#pragma once
#include <systemd/sd-event.h>
#include <chrono>
#include <functional>
#include <stdexcept>
namespace phosphor
{
/** @class Timer
* @brief Manages starting watchdog timers and handling timeouts
*/
class Timer
{
public:
/** @brief Only need the default Timer */
Timer() = delete;
Timer(const Timer&) = delete;
Timer& operator=(const Timer&) = delete;
Timer(Timer&&) = delete;
Timer& operator=(Timer&&) = delete;
/** @brief Constructs timer object
* Uses the default sd_event object
*
* @param[in] funcCallBack - optional function callback for timer
* expirations
*/
Timer(std::function<void()> userCallBack = nullptr) :
event(nullptr), eventSource(nullptr), expired(false),
userCallBack(userCallBack)
{
// take a reference to the default event object
sd_event_default(&event);
// Initialize the timer
initialize();
}
/** @brief Constructs timer object
*
* @param[in] events - sd_event pointer
* @param[in] funcCallBack - optional function callback for timer
* expirations
*/
Timer(sd_event* event, std::function<void()> userCallBack = nullptr) :
event(event), eventSource(nullptr), expired(false),
userCallBack(userCallBack)
{
if (!event)
{
// take a reference to the default event object
sd_event_default(&event);
}
else
{
// take a reference to the event; the caller can
// keep their ref or drop it without harm
sd_event_ref(event);
}
// Initialize the timer
initialize();
}
~Timer()
{
// the *_unref functions do nothing if passed nullptr
// so no need to check first
eventSource = sd_event_source_unref(eventSource);
event = sd_event_unref(event);
}
inline bool isExpired() const
{
return expired;
}
inline bool isRunning() const
{
int running = 0;
if (sd_event_source_get_enabled(eventSource, &running) < 0)
{
return false;
}
return running != SD_EVENT_OFF;
}
/** @brief Starts the timer with specified expiration value.
* input is an offset from the current steady_clock
*/
int start(std::chrono::microseconds usec, bool periodic = false)
{
// Disable the timer
stop();
expired = false;
duration = usec;
if (periodic)
{
// A periodic timer means that when the timer goes off,
// it automatically rearms and starts running again
runType = SD_EVENT_ON;
}
else
{
// A ONESHOT timer means that when the timer goes off,
// it moves to disabled state.
runType = SD_EVENT_ONESHOT;
}
// Get the current MONOTONIC time and add the delta
auto expireTime = getTime() + usec;
// Set the time
int r = sd_event_source_set_time(eventSource, expireTime.count());
if (r < 0)
{
throw std::runtime_error("Failure to set timer");
}
r = setEnabled(runType);
if (r < 0)
{
throw std::runtime_error("Failure to start timer");
}
return r;
}
int stop()
{
return setEnabled(SD_EVENT_OFF);
}
private:
/** @brief the sd_event structure */
sd_event* event;
/** @brief Source of events */
sd_event_source* eventSource;
/** @brief Returns if the associated timer is expired
*
* This is set to true when the timeoutHandler is called into
*/
bool expired;
/** @brief Optional function to call on timer expiration */
std::function<void()> userCallBack;
/** @brief timer duration */
std::chrono::microseconds duration;
/** @brief timer run type (oneshot or periodic) */
int runType;
/** @brief Initializes the timer object with infinite
* expiration time and sets up the callback handler
*
* @return None.
*
* @error std::runtime exception thrown
*/
void initialize()
{
if (!event)
{
throw std::runtime_error("Timer has no event loop");
}
// This can not be called more than once.
if (eventSource)
{
throw std::runtime_error("Timer already initialized");
}
// Add infinite expiration time
auto r = sd_event_add_time(
event, &eventSource,
CLOCK_MONOTONIC, // Time base
UINT64_MAX, // Expire time - way long time
0, // Use default event accuracy
[](sd_event_source* /*eventSource*/, uint64_t /*usec*/,
void* userData) {
auto timer = static_cast<Timer*>(userData);
return timer->timeoutHandler();
}, // Callback handler on timeout
this); // User data
if (r < 0)
{
throw std::runtime_error("Timer initialization failed");
}
// Disable the timer for now
r = stop();
if (r < 0)
{
throw std::runtime_error("Disabling the timer failed");
}
}
/** @brief Enables / disables the timer */
int setEnabled(int action)
{
return sd_event_source_set_enabled(eventSource, action);
}
/** @brief Callback function when timer goes off
*
* On getting the signal, initiate the hard power off request
*
* @param[in] eventSource - Source of the event
* @param[in] usec - time in micro seconds
* @param[in] userData - User data pointer
*
*/
int timeoutHandler()
{
if (runType == SD_EVENT_ON)
{
// set the event to the future
auto expireTime = getTime() + duration;
// Set the time
int r = sd_event_source_set_time(eventSource, expireTime.count());
if (r < 0)
{
throw std::runtime_error("Failure to set timer");
}
}
expired = true;
// Call optional user call back function if available
if (userCallBack)
{
userCallBack();
}
int running;
if (sd_event_source_get_enabled(eventSource, &running) < 0 ||
running == SD_EVENT_ONESHOT)
{
stop();
}
return 0;
}
/** @brief Gets the current time from steady clock */
static std::chrono::microseconds getTime()
{
using namespace std::chrono;
auto usec = steady_clock::now().time_since_epoch();
return duration_cast<microseconds>(usec);
}
};
} // namespace phosphor