From 2de67cf4d19e2e433e1ea56583e0d5ae043b3d79 Mon Sep 17 00:00:00 2001 From: Matt Spinler Date: Thu, 27 Apr 2017 11:07:53 -0500 Subject: phosphor-fan: Create timer class This class can be used to call an arbitrary function after a certain amount of time, which is set in microseconds. Change-Id: Ifd65bbf0c3482db4e37efc3b1ccc868e62fa0afa Signed-off-by: Matt Spinler --- Makefile.am | 3 +- timer.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ timer.hpp | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 timer.cpp create mode 100644 timer.hpp diff --git a/Makefile.am b/Makefile.am index 7b34537..61c6782 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,7 +7,8 @@ libfan_la_LIBADD = \ libfan_la_CXXFLAGS = $(SDBUSPLUS_CFLAGS) libfan_la_SOURCES = \ - utility.cpp + utility.cpp \ + timer.cpp SUBDIRS = . diff --git a/timer.cpp b/timer.cpp new file mode 100644 index 0000000..41a8138 --- /dev/null +++ b/timer.cpp @@ -0,0 +1,172 @@ +/** + * Copyright © 2017 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include "timer.hpp" + +namespace phosphor +{ +namespace fan +{ +namespace util +{ + +using namespace phosphor::logging; + +Timer::Timer(EventPtr& events, + std::function callbackFunc) : + timeEvent(events), + callback(callbackFunc), + timeout(0) +{ + sd_event_source* source = nullptr; + + // Start with an infinite expiration time + auto r = sd_event_add_time(timeEvent.get(), + &source, + CLOCK_MONOTONIC, // Time base + UINT64_MAX, // Expire time - way long time + 0, // Use default event accuracy + timeoutHandler, // Callback handler on timeout + this); // User data + if (r < 0) + { + log("Timer::Timer failed call to sd_event_add_time", + entry("ERROR=%s", strerror(-r))); + //TODO openbmc/openbmc#1555 throw an elog + throw std::runtime_error("Timer initialization failed"); + } + + eventSource.reset(source); + + //Ensure timer isn't running + setTimer(SD_EVENT_OFF); +} + + +Timer::~Timer() +{ + setTimer(SD_EVENT_OFF); +} + + +int Timer::timeoutHandler(sd_event_source* eventSource, + uint64_t usec, void* userData) +{ + auto timer = static_cast(userData); + + if (timer->type == TimerType::repeating) + { + //Set the next expiration time + timer->setTimeout(); + } + + timer->callback(); + + return 0; +} + + +std::chrono::microseconds Timer::getTime() +{ + using namespace std::chrono; + auto now = steady_clock::now().time_since_epoch(); + return duration_cast(now); +} + + +void Timer::setTimer(int action) +{ + auto r = sd_event_source_set_enabled(eventSource.get(), action); + if (r < 0) + { + log("Failed call to sd_event_source_set_enabled", + entry("ERROR=%s", strerror(-r)), + entry("ACTION=%d", action)); + //TODO openbmc/openbmc#1555 throw an elog + throw std::runtime_error("Failed call to sd_event_source_set_enabled"); + } +} + + +void Timer::stop() +{ + setTimer(SD_EVENT_OFF); +} + + +bool Timer::running() +{ + int status = 0; + + //returns SD_EVENT_OFF, SD_EVENT_ON, or SD_EVENT_ONESHOT + auto r = sd_event_source_get_enabled(eventSource.get(), &status); + if (r < 0) + { + log("Failed call to sd_event_source_get_enabled", + entry("ERROR=%s", strerror(-r))); + //TODO openbmc/openbmc#1555 throw an elog + throw std::runtime_error("Failed call to sd_event_source_get_enabled"); + } + + return (status != SD_EVENT_OFF); +} + + +void Timer::setTimeout() +{ + //Get the current time and add the delta + static_assert(std::is_same::value, + "Timer::getTime() is the wrong type"); + static_assert(std::is_same::value, + "Timer::timeout is the wrong type"); + + auto expireTime = getTime() + timeout; + + //Set the time + auto r = sd_event_source_set_time(eventSource.get(), expireTime.count()); + if (r < 0) + { + log("Failed call to sd_event_source_set_time", + entry("ERROR=%s", strerror(-r))); + //TODO openbmc/openbmc#1555 throw an elog + throw std::runtime_error("Failed call to sd_event_source_set_time"); + } +} + + +void Timer::start(std::chrono::microseconds timeValue, + TimerType timerType) +{ + type = timerType; + + // Disable the timer + setTimer(SD_EVENT_OFF); + + //Rearm the timer + timeout = timeValue; + setTimeout(); + + setTimer((type == TimerType::oneshot) ? SD_EVENT_ONESHOT : SD_EVENT_ON); +} + + +} +} +} diff --git a/timer.hpp b/timer.hpp new file mode 100644 index 0000000..ea28176 --- /dev/null +++ b/timer.hpp @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include +#include + +namespace phosphor +{ +namespace fan +{ +namespace util +{ + +struct EventSourceDeleter +{ + void operator()(sd_event_source* eventSource) const + { + sd_event_source_unref(eventSource); + } +}; + +using EventSourcePtr = std::unique_ptr; + +using EventPtr = std::shared_ptr; + +/** + * @class Timer + * + * This class implements a simple timer that runs an arbitrary + * function on expiration. The timeout value is set in microseconds. + * It can be stopped while it is running, and queried to see if it is + * running. + * + * If started with the 'repeating' argument, it will keep calling the + * callback function every microseconds. If started with the + * 'oneshot' argument, it will just call the callback function one time. + * + * It needs an sd_event loop to function. + */ +class Timer +{ + public: + + enum class TimerType + { + oneshot, + repeating + }; + + Timer() = delete; + Timer(const Timer&) = delete; + Timer& operator=(const Timer&) = delete; + Timer(Timer&&) = default; + Timer& operator=(Timer&&) = default; + + /** + * @brief Constructs timer object + * + * @param[in] events - sd_event pointer, previously created + * @param[in] callbackFunc - The function to call on timer expiration + */ + Timer(EventPtr& events, + std::function callbackFunc); + + /** + * @brief Destructor + */ + ~Timer(); + + /** + * @brief Starts the timer + * + * The input is an offset from the current steady clock. + * + * @param[in] usec - the timeout value in microseconds + * @param[in] type - either a oneshot, or repeating + */ + void start(std::chrono::microseconds usec, + TimerType type = TimerType::oneshot); + + /** + * @brief Stop the timer + */ + void stop(); + + /** + * @brief Returns true if the timer is running + */ + bool running(); + + /** + * @brief Returns the timeout value + * + * @return - the last value sent in via start(). + * + * Could be used to restart the timer with the same + * timeout. i.e. start(getTimeout()); + */ + inline auto getTimeout() const + { + return timeout; + } + + /** + * @brief Returns the timer type + */ + inline auto getType() const + { + return type; + } + + private: + + /** + * @brief Callback function when timer goes off + * + * Calls the callback function passed in by the user. + * + * @param[in] eventSource - Source of the event + * @param[in] usec - time in micro seconds + * @param[in] userData - User data pointer + */ + static int timeoutHandler(sd_event_source* eventSource, + uint64_t usec, void* userData); + + /** + * @brief Gets the current time from the steady clock + */ + std::chrono::microseconds getTime(); + + /** + * @brief Wrapper around sd_event_source_set_enabled + * + * @param[in] action - either SD_EVENT_OFF, SD_EVENT_ON, + * or SD_EVENT_ONESHOT + */ + void setTimer(int action); + + + /** + * @brief Sets the expiration time for the timer + * + * Sets it to timeout microseconds in the future + */ + void setTimeout(); + + /** + * @brief The sd_event structure + */ + EventPtr timeEvent; + + /** + * @brief Source of events + */ + EventSourcePtr eventSource; + + /** + * @brief Either 'repeating' or 'oneshot' + */ + TimerType type = TimerType::oneshot; + + /** + * @brief The function to call when the timer expires + */ + std::function callback; + + /** + * @brief What the timer was set to run for + * + * Not cleared on timer expiration + */ + std::chrono::microseconds timeout; +}; + +} +} +} -- cgit v1.2.1