blob: 69cbcb2a38f72592c12ef4adaa24bf35edf185e3 [file] [log] [blame]
Vernon Mauery12a4aec2018-09-05 13:43:19 -07001#pragma once
2
3#include <systemd/sd-event.h>
4
5#include <chrono>
6#include <functional>
Andrew Geissler03d6c012020-05-16 15:26:52 -05007#include <stdexcept>
Vernon Mauery12a4aec2018-09-05 13:43:19 -07008
9namespace phosphor
10{
11
12/** @class Timer
13 * @brief Manages starting watchdog timers and handling timeouts
14 */
15class 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] funcCallBack - optional function callback for timer
29 * expirations
30 */
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] events - sd_event pointer
44 * @param[in] funcCallBack - optional function callback for timer
45 * expirations
46 */
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
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
74 inline bool isExpired() const
75 {
76 return expired;
77 }
78
79 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 */
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
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 * @return None.
160 *
161 * @error std::runtime exception thrown
162 */
163 void initialize()
164 {
165 if (!event)
166 {
167 throw std::runtime_error("Timer has no event loop");
168 }
169 // This can not be called more than once.
170 if (eventSource)
171 {
172 throw std::runtime_error("Timer already initialized");
173 }
174
175 // Add infinite expiration time
176 auto r = sd_event_add_time(
177 event, &eventSource,
178 CLOCK_MONOTONIC, // Time base
179 UINT64_MAX, // Expire time - way long time
180 0, // Use default event accuracy
Patrick Williams78b78032020-05-20 10:32:05 -0500181 [](sd_event_source* /*eventSource*/, uint64_t /*usec*/,
182 void* userData) {
Vernon Mauery12a4aec2018-09-05 13:43:19 -0700183 auto timer = static_cast<Timer*>(userData);
184 return timer->timeoutHandler();
185 }, // Callback handler on timeout
186 this); // User data
187 if (r < 0)
188 {
189 throw std::runtime_error("Timer initialization failed");
190 }
191
192 // Disable the timer for now
193 r = stop();
194 if (r < 0)
195 {
196 throw std::runtime_error("Disabling the timer failed");
197 }
198 }
199
200 /** @brief Enables / disables the timer */
201 int setEnabled(int action)
202 {
203 return sd_event_source_set_enabled(eventSource, action);
204 }
205
206 /** @brief Callback function when timer goes off
207 *
208 * On getting the signal, initiate the hard power off request
209 *
210 * @param[in] eventSource - Source of the event
211 * @param[in] usec - time in micro seconds
212 * @param[in] userData - User data pointer
213 *
214 */
215 int timeoutHandler()
216 {
217 if (runType == SD_EVENT_ON)
218 {
219 // set the event to the future
220 auto expireTime = getTime() + duration;
221
222 // Set the time
223 int r = sd_event_source_set_time(eventSource, expireTime.count());
224 if (r < 0)
225 {
226 throw std::runtime_error("Failure to set timer");
227 }
228 }
229 expired = true;
230
231 // Call optional user call back function if available
232 if (userCallBack)
233 {
234 userCallBack();
235 }
236
237 int running;
238 if (sd_event_source_get_enabled(eventSource, &running) < 0 ||
239 running == SD_EVENT_ONESHOT)
240 {
241 stop();
242 }
243 return 0;
244 }
245
246 /** @brief Gets the current time from steady clock */
247 static std::chrono::microseconds getTime()
248 {
249 using namespace std::chrono;
250 auto usec = steady_clock::now().time_since_epoch();
251 return duration_cast<microseconds>(usec);
252 }
253};
254
255} // namespace phosphor