Vernon Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 1 | #pragma once |
| 2 | |
| 3 | #include <systemd/sd-event.h> |
| 4 | |
| 5 | #include <chrono> |
| 6 | #include <functional> |
Andrew Geissler | 03d6c01 | 2020-05-16 15:26:52 -0500 | [diff] [blame] | 7 | #include <stdexcept> |
Vernon Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 8 | |
| 9 | namespace phosphor |
| 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 | * |
Ed Tanous | ddc57bd | 2023-01-05 10:47:32 -0800 | [diff] [blame] | 28 | * @param[in] userCallBack - optional function callback for timer |
Vernon Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 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 | * |
Ed Tanous | ddc57bd | 2023-01-05 10:47:32 -0800 | [diff] [blame] | 43 | * @param[in] event - sd_event pointer |
| 44 | * @param[in] userCallBack - optional function callback for timer |
Vernon Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 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 | * |
Vernon Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 159 | * @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 Williams | 78b7803 | 2020-05-20 10:32:05 -0500 | [diff] [blame] | 179 | [](sd_event_source* /*eventSource*/, uint64_t /*usec*/, |
| 180 | void* userData) { |
Patrick Williams | d214904 | 2023-05-10 07:50:13 -0500 | [diff] [blame] | 181 | auto timer = static_cast<Timer*>(userData); |
| 182 | return timer->timeoutHandler(); |
Vernon Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 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 */ |
| 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 Mauery | 12a4aec | 2018-09-05 13:43:19 -0700 | [diff] [blame] | 208 | */ |
| 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 |