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