xref: /openbmc/sdbusplus/include/sdbusplus/timer.hpp (revision 06f265f6f18e22b1fb68761edb50ecd6722c2a47)
1 #pragma once
2 
3 #include <systemd/sd-event.h>
4 
5 #include <chrono>
6 #include <functional>
7 #include <stdexcept>
8 
9 namespace sdbusplus
10 {
11 
12 /** @class Timer
13  *  @brief Manages starting watchdog timers and handling timeouts
14  */
15 class Timer
16 {
17   public:
18     /** @brief Only need the default Timer */
19     Timer() = delete;
20     Timer(const Timer&) = delete;
21     Timer& operator=(const Timer&) = delete;
22     Timer(Timer&&) = delete;
23     Timer& operator=(Timer&&) = delete;
24 
25     /** @brief Constructs timer object
26      *         Uses the default sd_event object
27      *
28      *  @param[in] userCallBack - optional function callback for timer
29      *                            expirations
30      */
Timer(std::function<void ()> userCallBack=nullptr)31     Timer(std::function<void()> userCallBack = nullptr) :
32         event(nullptr), eventSource(nullptr), expired(false),
33         userCallBack(userCallBack)
34     {
35         // take a reference to the default event object
36         sd_event_default(&event);
37         // Initialize the timer
38         initialize();
39     }
40 
41     /** @brief Constructs timer object
42      *
43      *  @param[in] event - sd_event pointer
44      *  @param[in] userCallBack - optional function callback for timer
45      *                            expirations
46      */
Timer(sd_event * event,std::function<void ()> userCallBack=nullptr)47     Timer(sd_event* event, std::function<void()> userCallBack = nullptr) :
48         event(event), eventSource(nullptr), expired(false),
49         userCallBack(userCallBack)
50     {
51         if (!event)
52         {
53             // take a reference to the default event object
54             sd_event_default(&event);
55         }
56         else
57         {
58             // take a reference to the event; the caller can
59             // keep their ref or drop it without harm
60             sd_event_ref(event);
61         }
62         // Initialize the timer
63         initialize();
64     }
65 
~Timer()66     ~Timer()
67     {
68         // the *_unref functions do nothing if passed nullptr
69         // so no need to check first
70         eventSource = sd_event_source_unref(eventSource);
71         event = sd_event_unref(event);
72     }
73 
isExpired() const74     inline bool isExpired() const
75     {
76         return expired;
77     }
78 
isRunning() const79     inline bool isRunning() const
80     {
81         int running = 0;
82         if (sd_event_source_get_enabled(eventSource, &running) < 0)
83         {
84             return false;
85         }
86         return running != SD_EVENT_OFF;
87     }
88 
89     /** @brief Starts the timer with specified expiration value.
90      *  input is an offset from the current steady_clock
91      */
start(std::chrono::microseconds usec,bool periodic=false)92     int start(std::chrono::microseconds usec, bool periodic = false)
93     {
94         // Disable the timer
95         stop();
96         expired = false;
97         duration = usec;
98         if (periodic)
99         {
100             // A periodic timer means that when the timer goes off,
101             // it automatically rearms and starts running again
102             runType = SD_EVENT_ON;
103         }
104         else
105         {
106             // A ONESHOT timer means that when the timer goes off,
107             // it moves to disabled state.
108             runType = SD_EVENT_ONESHOT;
109         }
110 
111         // Get the current MONOTONIC time and add the delta
112         auto expireTime = getTime() + usec;
113 
114         // Set the time
115         int r = sd_event_source_set_time(eventSource, expireTime.count());
116         if (r < 0)
117         {
118             throw std::runtime_error("Failure to set timer");
119         }
120 
121         r = setEnabled(runType);
122         if (r < 0)
123         {
124             throw std::runtime_error("Failure to start timer");
125         }
126         return r;
127     }
128 
stop()129     int stop()
130     {
131         return setEnabled(SD_EVENT_OFF);
132     }
133 
134   private:
135     /** @brief the sd_event structure */
136     sd_event* event;
137 
138     /** @brief Source of events */
139     sd_event_source* eventSource;
140 
141     /** @brief Returns if the associated timer is expired
142      *
143      *  This is set to true when the timeoutHandler is called into
144      */
145     bool expired;
146 
147     /** @brief Optional function to call on timer expiration */
148     std::function<void()> userCallBack;
149 
150     /** @brief timer duration */
151     std::chrono::microseconds duration;
152 
153     /** @brief timer run type (oneshot or periodic) */
154     int runType;
155 
156     /** @brief Initializes the timer object with infinite
157      *         expiration time and sets up the callback handler
158      *
159      *  @error std::runtime exception thrown
160      */
initialize()161     void initialize()
162     {
163         if (!event)
164         {
165             throw std::runtime_error("Timer has no event loop");
166         }
167         // This can not be called more than once.
168         if (eventSource)
169         {
170             throw std::runtime_error("Timer already initialized");
171         }
172 
173         // Add infinite expiration time
174         auto r = sd_event_add_time(
175             event, &eventSource,
176             CLOCK_MONOTONIC, // Time base
177             UINT64_MAX,      // Expire time - way long time
178             0,               // Use default event accuracy
179             [](sd_event_source* /*eventSource*/, uint64_t /*usec*/,
180                void* userData) {
181                 auto timer = static_cast<Timer*>(userData);
182                 return timer->timeoutHandler();
183             },     // Callback handler on timeout
184             this); // User data
185         if (r < 0)
186         {
187             throw std::runtime_error("Timer initialization failed");
188         }
189 
190         // Disable the timer for now
191         r = stop();
192         if (r < 0)
193         {
194             throw std::runtime_error("Disabling the timer failed");
195         }
196     }
197 
198     /** @brief Enables / disables the timer */
setEnabled(int action)199     int setEnabled(int action)
200     {
201         return sd_event_source_set_enabled(eventSource, action);
202     }
203 
204     /** @brief Callback function when timer goes off
205      *
206      *  On getting the signal, initiate the hard power off request
207      *
208      */
timeoutHandler()209     int timeoutHandler()
210     {
211         if (runType == SD_EVENT_ON)
212         {
213             // set the event to the future
214             auto expireTime = getTime() + duration;
215 
216             // Set the time
217             int r = sd_event_source_set_time(eventSource, expireTime.count());
218             if (r < 0)
219             {
220                 throw std::runtime_error("Failure to set timer");
221             }
222         }
223         expired = true;
224 
225         // Call optional user call back function if available
226         if (userCallBack)
227         {
228             userCallBack();
229         }
230 
231         int running;
232         if (sd_event_source_get_enabled(eventSource, &running) < 0 ||
233             running == SD_EVENT_ONESHOT)
234         {
235             stop();
236         }
237         return 0;
238     }
239 
240     /** @brief Gets the current time from steady clock */
getTime()241     static std::chrono::microseconds getTime()
242     {
243         using namespace std::chrono;
244         auto usec = steady_clock::now().time_since_epoch();
245         return duration_cast<microseconds>(usec);
246     }
247 };
248 
249 } // namespace sdbusplus
250