1 #include "scheduled_host_transition.hpp"
2
3 #include "utils.hpp"
4 #include "xyz/openbmc_project/State/Host/server.hpp"
5
6 #include <sys/timerfd.h>
7 #include <unistd.h>
8
9 #include <cereal/archives/json.hpp>
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/elog.hpp>
12 #include <phosphor-logging/lg2.hpp>
13 #include <xyz/openbmc_project/ScheduledTime/error.hpp>
14 #include <xyz/openbmc_project/State/Host/error.hpp>
15
16 #include <chrono>
17 #include <filesystem>
18 #include <fstream>
19
20 // Need to do this since its not exported outside of the kernel.
21 // Refer : https://gist.github.com/lethean/446cea944b7441228298
22 #ifndef TFD_TIMER_CANCEL_ON_SET
23 #define TFD_TIMER_CANCEL_ON_SET (1 << 1)
24 #endif
25
26 namespace phosphor
27 {
28 namespace state
29 {
30 namespace manager
31 {
32
33 PHOSPHOR_LOG2_USING;
34
35 namespace fs = std::filesystem;
36
37 using namespace std::chrono;
38 using namespace phosphor::logging;
39 using namespace xyz::openbmc_project::ScheduledTime;
40 using InvalidTimeError =
41 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
42 using HostTransition =
43 sdbusplus::server::xyz::openbmc_project::state::ScheduledHostTransition;
44 using HostState = sdbusplus::server::xyz::openbmc_project::state::Host;
45
scheduledTime(uint64_t value)46 uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
47 {
48 info("A scheduled host transition request has been made for {TIME}", "TIME",
49 value);
50 if (value == 0)
51 {
52 // 0 means the function Scheduled Host Transition is disabled
53 // Stop the timer if it's running
54 if (timer.isEnabled())
55 {
56 timer.setEnabled(false);
57 debug(
58 "scheduledTime: The function Scheduled Host Transition is disabled.");
59 }
60 }
61 else
62 {
63 auto deltaTime = seconds(value) - getTime();
64 if (deltaTime < seconds(0))
65 {
66 error(
67 "Scheduled time is earlier than current time. Fail to schedule host transition.");
68 elog<InvalidTimeError>(
69 InvalidTime::REASON("Scheduled time is in the past"));
70 }
71 else
72 {
73 // Start a timer to do host transition at scheduled time
74 timer.restart(deltaTime);
75 }
76 }
77
78 // Set scheduledTime
79 HostTransition::scheduledTime(value);
80 // Store scheduled values
81 serializeScheduledValues();
82
83 return value;
84 }
85
getTime()86 seconds ScheduledHostTransition::getTime()
87 {
88 auto now = system_clock::now();
89 return duration_cast<seconds>(now.time_since_epoch());
90 }
91
hostTransition()92 void ScheduledHostTransition::hostTransition()
93 {
94 auto hostName = std::string(HostState::namespace_path::host) +
95 std::to_string(id);
96 std::string hostPath =
97 sdbusplus::message::object_path(HostState::namespace_path::value) /
98 hostName;
99
100 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
101
102 info("Trying to set requestedTransition to {REQUESTED_TRANSITION}",
103 "REQUESTED_TRANSITION", reqTrans);
104
105 utils::setProperty(bus, hostPath, HostState::interface,
106 HostState::property_names::requested_host_transition,
107 reqTrans);
108
109 // Set RestartCause to indicate this transition is occurring due to a
110 // scheduled host transition as long as it's not an off request
111 if (HostTransition::scheduledTransition() != HostState::Transition::Off)
112 {
113 info("Set RestartCause to scheduled power on reason");
114 auto resCause =
115 convertForMessage(HostState::RestartCause::ScheduledPowerOn);
116 utils::setProperty(bus, hostPath, HostState::interface,
117 HostState::property_names::restart_cause, resCause);
118 }
119 }
120
callback()121 void ScheduledHostTransition::callback()
122 {
123 // Stop timer, since we need to do host transition once only
124 timer.setEnabled(false);
125 hostTransition();
126 // Set scheduledTime to 0 to disable host transition and update scheduled
127 // values
128 scheduledTime(0);
129 }
130
initialize()131 void ScheduledHostTransition::initialize()
132 {
133 // Subscribe time change event
134 // Choose the MAX time that is possible to avoid misfires.
135 constexpr itimerspec maxTime = {
136 {0, 0}, // it_interval
137 {system_clock::duration::max().count(), 0}, // it_value
138 };
139
140 // Create and operate on a timer that delivers timer expiration
141 // notifications via a file descriptor.
142 timeFd = timerfd_create(CLOCK_REALTIME, 0);
143 if (timeFd == -1)
144 {
145 auto eno = errno;
146 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
147 eno, "RC", timeFd);
148 throw std::system_error(eno, std::system_category());
149 }
150
151 // Starts the timer referred to by the file descriptor fd.
152 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
153 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
154 // as cancelable if the real-time clock undergoes a discontinuous change.
155 // In this way, we can monitor whether BMC time is changed or not.
156 auto r = timerfd_settime(
157 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
158 if (r != 0)
159 {
160 auto eno = errno;
161 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
162 "RC", r);
163 throw std::system_error(eno, std::system_category());
164 }
165
166 sd_event_source* es;
167 // Add a new I/O event source to an event loop. onTimeChange will be called
168 // when the event source is triggered.
169 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
170 if (r < 0)
171 {
172 auto eno = errno;
173 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
174 "RC", r);
175 throw std::system_error(eno, std::system_category());
176 }
177 timeChangeEventSource.reset(es);
178 }
179
~ScheduledHostTransition()180 ScheduledHostTransition::~ScheduledHostTransition()
181 {
182 close(timeFd);
183 }
184
handleTimeUpdates()185 void ScheduledHostTransition::handleTimeUpdates()
186 {
187 // Stop the timer if it's running.
188 // Don't return directly when timer is stopped, because the timer is always
189 // disabled after the BMC is rebooted.
190 if (timer.isEnabled())
191 {
192 timer.setEnabled(false);
193 }
194
195 // Get scheduled time
196 auto schedTime = HostTransition::scheduledTime();
197
198 if (schedTime == 0)
199 {
200 debug(
201 "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
202 return;
203 }
204
205 auto deltaTime = seconds(schedTime) - getTime();
206 if (deltaTime <= seconds(0))
207 {
208 try
209 {
210 hostTransition();
211 }
212 catch (const sdbusplus::exception_t& e)
213 {
214 using BMCNotReady = sdbusplus::error::xyz::openbmc_project::state::
215 host::BMCNotReady;
216 // If error indicates BMC is not at Ready error then reschedule for
217 // 60s later
218 if ((e.name() != nullptr) &&
219 (e.name() == std::string_view(BMCNotReady::errName)))
220 {
221 warning(
222 "BMC is not at ready, reschedule transition request for 60s");
223 timer.restart(seconds(60));
224 return;
225 }
226 else
227 {
228 throw;
229 }
230 }
231 // Set scheduledTime to 0 to disable host transition and update
232 // scheduled values
233 scheduledTime(0);
234 }
235 else
236 {
237 // Start a timer to do host transition at scheduled time
238 timer.restart(deltaTime);
239 }
240 }
241
onTimeChange(sd_event_source *,int fd,uint32_t,void * userdata)242 int ScheduledHostTransition::onTimeChange(
243 sd_event_source* /* es */, int fd, uint32_t /* revents */, void* userdata)
244 {
245 auto* schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
246
247 std::array<char, 64> time{};
248
249 // We are not interested in the data here.
250 // So read until there is no new data here in the FD
251 while (read(fd, time.data(), time.max_size()) > 0)
252 {}
253
254 debug("BMC system time is changed");
255 schedHostTran->handleTimeUpdates();
256
257 return 0;
258 }
259
serializeScheduledValues()260 void ScheduledHostTransition::serializeScheduledValues()
261 {
262 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
263 std::ofstream os(path.c_str(), std::ios::binary);
264 cereal::JSONOutputArchive oarchive(os);
265
266 oarchive(HostTransition::scheduledTime(),
267 HostTransition::scheduledTransition());
268 }
269
deserializeScheduledValues(uint64_t & time,Transition & trans)270 bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
271 Transition& trans)
272 {
273 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
274
275 try
276 {
277 if (fs::exists(path))
278 {
279 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
280 cereal::JSONInputArchive iarchive(is);
281 iarchive(time, trans);
282 return true;
283 }
284 }
285 catch (const std::exception& e)
286 {
287 error("deserialize exception: {ERROR}", "ERROR", e);
288 fs::remove(path);
289 }
290
291 return false;
292 }
293
restoreScheduledValues()294 void ScheduledHostTransition::restoreScheduledValues()
295 {
296 uint64_t time;
297 Transition trans;
298 if (!deserializeScheduledValues(time, trans))
299 {
300 // set to default value
301 HostTransition::scheduledTime(0);
302 HostTransition::scheduledTransition(Transition::On);
303 }
304 else
305 {
306 HostTransition::scheduledTime(time);
307 HostTransition::scheduledTransition(trans);
308 // Rebooting BMC is something like the BMC time is changed,
309 // so go on with the same process as BMC time changed.
310 handleTimeUpdates();
311 }
312 }
313
314 } // namespace manager
315 } // namespace state
316 } // namespace phosphor
317