blob: 4bf16bd56390d5905a06e7e21b805243cc45a426 [file] [log] [blame]
Carol Wang71230ef2020-02-18 17:39:49 +08001#include "scheduled_host_transition.hpp"
Carol Wangdc059392020-03-13 17:39:17 +08002#include "utils.hpp"
Carol Wang71230ef2020-02-18 17:39:49 +08003
Carol Wang4ca6f3f2020-02-19 16:28:59 +08004#include <phosphor-logging/elog-errors.hpp>
5#include <phosphor-logging/elog.hpp>
6#include <phosphor-logging/log.hpp>
7#include <xyz/openbmc_project/ScheduledTime/error.hpp>
Carol Wang1dbbef42020-03-09 11:51:23 +08008#include <cereal/archives/json.hpp>
Carol Wang4ca6f3f2020-02-19 16:28:59 +08009#include <chrono>
Carol Wang1dbbef42020-03-09 11:51:23 +080010#include <filesystem>
11#include <fstream>
Carol Wangef7abe12020-02-25 16:19:34 +080012#include <sys/timerfd.h>
13#include <unistd.h>
14
15// Need to do this since its not exported outside of the kernel.
16// Refer : https://gist.github.com/lethean/446cea944b7441228298
17#ifndef TFD_TIMER_CANCEL_ON_SET
18#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
19#endif
20
21// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
22#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
Carol Wang4ca6f3f2020-02-19 16:28:59 +080023
Carol Wang71230ef2020-02-18 17:39:49 +080024namespace phosphor
25{
26namespace state
27{
28namespace manager
29{
30
Carol Wang1dbbef42020-03-09 11:51:23 +080031namespace fs = std::filesystem;
32
Carol Wang4ca6f3f2020-02-19 16:28:59 +080033using namespace std::chrono;
34using namespace phosphor::logging;
35using namespace xyz::openbmc_project::ScheduledTime;
36using InvalidTimeError =
37 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
Carol Wang71230ef2020-02-18 17:39:49 +080038using HostTransition =
39 sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
40
Carol Wang6a5db3d2020-02-21 10:12:01 +080041constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
42
Carol Wang71230ef2020-02-18 17:39:49 +080043uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
44{
Carol Wang4ca6f3f2020-02-19 16:28:59 +080045 if (value == 0)
46 {
47 // 0 means the function Scheduled Host Transition is disabled
Carol Wang6a5db3d2020-02-21 10:12:01 +080048 // Stop the timer if it's running
49 if (timer.isEnabled())
50 {
51 timer.setEnabled(false);
Carol Wang1dbbef42020-03-09 11:51:23 +080052 log<level::INFO>("scheduledTime: The function Scheduled Host "
53 "Transition is disabled.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080054 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080055 }
56 else
57 {
Carol Wang6a5db3d2020-02-21 10:12:01 +080058 auto deltaTime = seconds(value) - getTime();
59 if (deltaTime < seconds(0))
60 {
61 log<level::ERR>(
62 "Scheduled time is earlier than current time. Fail to "
63 "schedule host transition.");
64 elog<InvalidTimeError>(
65 InvalidTime::REASON("Scheduled time is in the past"));
66 }
67 else
68 {
69 // Start a timer to do host transition at scheduled time
70 timer.restart(deltaTime);
71 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080072 }
Carol Wang6a5db3d2020-02-21 10:12:01 +080073
Carol Wang1dbbef42020-03-09 11:51:23 +080074 // Set scheduledTime
75 HostTransition::scheduledTime(value);
76 // Store scheduled values
77 serializeScheduledValues();
78
79 return value;
Carol Wang71230ef2020-02-18 17:39:49 +080080}
81
Carol Wang4ca6f3f2020-02-19 16:28:59 +080082seconds ScheduledHostTransition::getTime()
83{
84 auto now = system_clock::now();
85 return duration_cast<seconds>(now.time_since_epoch());
86}
87
Carol Wang6a5db3d2020-02-21 10:12:01 +080088void ScheduledHostTransition::hostTransition()
89{
90 auto hostPath = std::string{HOST_OBJPATH} + '0';
91
92 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
93
Carol Wangdc059392020-03-13 17:39:17 +080094 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
95 reqTrans);
Carol Wang6a5db3d2020-02-21 10:12:01 +080096
97 log<level::INFO>("Set requestedTransition",
98 entry("REQUESTED_TRANSITION=%s", reqTrans.c_str()));
99}
100
101void ScheduledHostTransition::callback()
102{
103 // Stop timer, since we need to do host transition once only
104 timer.setEnabled(false);
105 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800106 // Set scheduledTime to 0 to disable host transition and update scheduled
107 // values
108 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800109}
110
Carol Wangef7abe12020-02-25 16:19:34 +0800111void ScheduledHostTransition::initialize()
112{
113 // Subscribe time change event
114 // Choose the MAX time that is possible to avoid mis fires.
115 constexpr itimerspec maxTime = {
116 {0, 0}, // it_interval
117 {TIME_T_MAX, 0}, // it_value
118 };
119
120 // Create and operate on a timer that delivers timer expiration
121 // notifications via a file descriptor.
122 timeFd = timerfd_create(CLOCK_REALTIME, 0);
123 if (timeFd == -1)
124 {
125 auto eno = errno;
126 log<level::ERR>("Failed to create timerfd", entry("ERRNO=%d", eno),
127 entry("RC=%d", timeFd));
128 throw std::system_error(eno, std::system_category());
129 }
130
131 // Starts the timer referred to by the file descriptor fd.
132 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
133 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
134 // as cancelable if the real-time clock undergoes a discontinuous change.
135 // In this way, we can monitor whether BMC time is changed or not.
136 auto r = timerfd_settime(
137 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
138 if (r != 0)
139 {
140 auto eno = errno;
141 log<level::ERR>("Failed to set timerfd", entry("ERRNO=%d", eno),
142 entry("RC=%d", r));
143 throw std::system_error(eno, std::system_category());
144 }
145
146 sd_event_source* es;
147 // Add a new I/O event source to an event loop. onTimeChange will be called
148 // when the event source is triggered.
149 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
150 if (r < 0)
151 {
152 auto eno = errno;
153 log<level::ERR>("Failed to add event", entry("ERRNO=%d", eno),
154 entry("RC=%d", r));
155 throw std::system_error(eno, std::system_category());
156 }
157 timeChangeEventSource.reset(es);
158}
159
160ScheduledHostTransition::~ScheduledHostTransition()
161{
162 close(timeFd);
163}
164
165void ScheduledHostTransition::handleTimeUpdates()
166{
Carol Wang1dbbef42020-03-09 11:51:23 +0800167 // Stop the timer if it's running.
168 // Don't return directly when timer is stopped, because the timer is always
169 // disabled after the BMC is rebooted.
170 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800171 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800172 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800173 }
Carol Wangef7abe12020-02-25 16:19:34 +0800174
175 // Get scheduled time
176 auto schedTime = HostTransition::scheduledTime();
177
178 if (schedTime == 0)
179 {
180 log<level::INFO>("handleTimeUpdates: The function Scheduled Host "
181 "Transition is disabled.");
182 return;
183 }
184
185 auto deltaTime = seconds(schedTime) - getTime();
186 if (deltaTime <= seconds(0))
187 {
Carol Wangef7abe12020-02-25 16:19:34 +0800188 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800189 // Set scheduledTime to 0 to disable host transition and update
190 // scheduled values
191 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800192 }
193 else
194 {
195 // Start a timer to do host transition at scheduled time
196 timer.restart(deltaTime);
197 }
198}
199
200int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
201 uint32_t /* revents */,
202 void* userdata)
203{
204 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
205
206 std::array<char, 64> time{};
207
208 // We are not interested in the data here.
209 // So read until there is no new data here in the FD
210 while (read(fd, time.data(), time.max_size()) > 0)
211 ;
212
213 log<level::INFO>("BMC system time is changed");
214 schedHostTran->handleTimeUpdates();
215
216 return 0;
217}
218
Carol Wang1dbbef42020-03-09 11:51:23 +0800219void ScheduledHostTransition::serializeScheduledValues()
220{
221 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
222 std::ofstream os(path.c_str(), std::ios::binary);
223 cereal::JSONOutputArchive oarchive(os);
224
225 oarchive(HostTransition::scheduledTime(),
226 HostTransition::scheduledTransition());
227}
228
229bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
230 Transition& trans)
231{
232 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
233
234 try
235 {
236 if (fs::exists(path))
237 {
238 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
239 cereal::JSONInputArchive iarchive(is);
240 iarchive(time, trans);
241 return true;
242 }
243 }
244 catch (std::exception& e)
245 {
246 log<level::ERR>(e.what());
247 fs::remove(path);
248 }
249
250 return false;
251}
252
253void ScheduledHostTransition::restoreScheduledValues()
254{
255 uint64_t time;
256 Transition trans;
257 if (!deserializeScheduledValues(time, trans))
258 {
259 // set to default value
260 HostTransition::scheduledTime(0);
261 HostTransition::scheduledTransition(Transition::On);
262 }
263 else
264 {
265 HostTransition::scheduledTime(time);
266 HostTransition::scheduledTransition(trans);
267 // Rebooting BMC is something like the BMC time is changed,
268 // so go on with the same process as BMC time changed.
269 handleTimeUpdates();
270 }
271}
272
Carol Wang71230ef2020-02-18 17:39:49 +0800273} // namespace manager
274} // namespace state
275} // namespace phosphor