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