blob: 2401763f9e7514085304ecb58c4f454b75bdbdee [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 *
Ed Tanousddc57bd2023-01-05 10:47:32 -080028 * @param[in] userCallBack - optional function callback for timer
Vernon Mauery12a4aec2018-09-05 13:43:19 -070029 * 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 *
Ed Tanousddc57bd2023-01-05 10:47:32 -080043 * @param[in] event - sd_event pointer
44 * @param[in] userCallBack - optional function callback for timer
Vernon Mauery12a4aec2018-09-05 13:43:19 -070045 * 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 *
Vernon Mauery12a4aec2018-09-05 13:43:19 -0700159 * @error std::runtime exception thrown
160 */
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
Patrick Williams78b78032020-05-20 10:32:05 -0500179 [](sd_event_source* /*eventSource*/, uint64_t /*usec*/,
180 void* userData) {
Patrick Williamsd2149042023-05-10 07:50:13 -0500181 auto timer = static_cast<Timer*>(userData);
182 return timer->timeoutHandler();
Vernon Mauery12a4aec2018-09-05 13:43:19 -0700183 }, // 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 */
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 *
Vernon Mauery12a4aec2018-09-05 13:43:19 -0700208 */
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 */
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 phosphor