blob: c827807d3e1d4cc2e66d3d86d60a5ee63ce1e576 [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"
Andrew Geissler68a8c312021-12-03 14:26:23 -06004#include "xyz/openbmc_project/State/Host/server.hpp"
Carol Wang71230ef2020-02-18 17:39:49 +08005
Andrew Geisslere426b582020-05-28 12:40:55 -05006#include <sys/timerfd.h>
7#include <unistd.h>
8
9#include <cereal/archives/json.hpp>
Carol Wang4ca6f3f2020-02-19 16:28:59 +080010#include <phosphor-logging/elog-errors.hpp>
11#include <phosphor-logging/elog.hpp>
Andrew Geissler8ffdb262021-09-20 15:25:19 -050012#include <phosphor-logging/lg2.hpp>
Carol Wang4ca6f3f2020-02-19 16:28:59 +080013#include <xyz/openbmc_project/ScheduledTime/error.hpp>
Andrew Geisslere426b582020-05-28 12:40:55 -050014
Carol Wang4ca6f3f2020-02-19 16:28:59 +080015#include <chrono>
Carol Wang1dbbef42020-03-09 11:51:23 +080016#include <filesystem>
17#include <fstream>
Carol Wangef7abe12020-02-25 16:19:34 +080018
19// Need to do this since its not exported outside of the kernel.
20// Refer : https://gist.github.com/lethean/446cea944b7441228298
21#ifndef TFD_TIMER_CANCEL_ON_SET
22#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
23#endif
24
Carol Wang71230ef2020-02-18 17:39:49 +080025namespace phosphor
26{
27namespace state
28{
29namespace manager
30{
31
Andrew Geissler8ffdb262021-09-20 15:25:19 -050032PHOSPHOR_LOG2_USING;
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 =
Patrick Williams7e969cb2023-08-23 16:24:23 -050042 sdbusplus::server::xyz::openbmc_project::state::ScheduledHostTransition;
43using HostState = sdbusplus::server::xyz::openbmc_project::state::Host;
Carol Wang71230ef2020-02-18 17:39:49 +080044
Carol Wang6a5db3d2020-02-21 10:12:01 +080045constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
Andrew Geissler68a8c312021-12-03 14:26:23 -060046constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause";
Carol Wang6a5db3d2020-02-21 10:12:01 +080047
Carol Wang71230ef2020-02-18 17:39:49 +080048uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
49{
Andrew Geissler60a238d2024-01-03 07:29:45 -060050 info("A scheduled host transtion request has been made for {TIME}", "TIME",
51 value);
Carol Wang4ca6f3f2020-02-19 16:28:59 +080052 if (value == 0)
53 {
54 // 0 means the function Scheduled Host Transition is disabled
Carol Wang6a5db3d2020-02-21 10:12:01 +080055 // Stop the timer if it's running
56 if (timer.isEnabled())
57 {
58 timer.setEnabled(false);
Andrew Geisslerad65b2d2021-09-21 12:53:29 -050059 debug(
60 "scheduledTime: The function Scheduled Host Transition is disabled.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080061 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080062 }
63 else
64 {
Carol Wang6a5db3d2020-02-21 10:12:01 +080065 auto deltaTime = seconds(value) - getTime();
66 if (deltaTime < seconds(0))
67 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -050068 error(
69 "Scheduled time is earlier than current time. Fail to schedule host transition.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080070 elog<InvalidTimeError>(
71 InvalidTime::REASON("Scheduled time is in the past"));
72 }
73 else
74 {
75 // Start a timer to do host transition at scheduled time
76 timer.restart(deltaTime);
77 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080078 }
Carol Wang6a5db3d2020-02-21 10:12:01 +080079
Carol Wang1dbbef42020-03-09 11:51:23 +080080 // Set scheduledTime
81 HostTransition::scheduledTime(value);
82 // Store scheduled values
83 serializeScheduledValues();
84
85 return value;
Carol Wang71230ef2020-02-18 17:39:49 +080086}
87
Carol Wang4ca6f3f2020-02-19 16:28:59 +080088seconds ScheduledHostTransition::getTime()
89{
90 auto now = system_clock::now();
91 return duration_cast<seconds>(now.time_since_epoch());
92}
93
Carol Wang6a5db3d2020-02-21 10:12:01 +080094void ScheduledHostTransition::hostTransition()
95{
Patrick Williams211d9722022-04-07 21:24:33 -050096 auto hostPath = std::string{HOST_OBJPATH} + std::to_string(id);
Carol Wang6a5db3d2020-02-21 10:12:01 +080097
Andrew Geissler60a238d2024-01-03 07:29:45 -060098 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
99
100 info("Trying to set requestedTransition to {REQUESTED_TRANSITION}",
101 "REQUESTED_TRANSITION", reqTrans);
102
103 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
104 reqTrans);
105
Andrew Geissler68a8c312021-12-03 14:26:23 -0600106 // Set RestartCause to indicate this transition is occurring due to a
107 // scheduled host transition as long as it's not an off request
108 if (HostTransition::scheduledTransition() != HostState::Transition::Off)
109 {
110 info("Set RestartCause to scheduled power on reason");
111 auto resCause =
112 convertForMessage(HostState::RestartCause::ScheduledPowerOn);
113 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE,
114 resCause);
115 }
Carol Wang6a5db3d2020-02-21 10:12:01 +0800116}
117
118void ScheduledHostTransition::callback()
119{
120 // Stop timer, since we need to do host transition once only
121 timer.setEnabled(false);
122 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800123 // Set scheduledTime to 0 to disable host transition and update scheduled
124 // values
125 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800126}
127
Carol Wangef7abe12020-02-25 16:19:34 +0800128void ScheduledHostTransition::initialize()
129{
130 // Subscribe time change event
131 // Choose the MAX time that is possible to avoid mis fires.
132 constexpr itimerspec maxTime = {
Andrew Geissler63f7b1a2023-07-24 14:54:58 -0400133 {0, 0}, // it_interval
134 {system_clock::duration::max().count(), 0}, // it_value
Carol Wangef7abe12020-02-25 16:19:34 +0800135 };
136
137 // Create and operate on a timer that delivers timer expiration
138 // notifications via a file descriptor.
139 timeFd = timerfd_create(CLOCK_REALTIME, 0);
140 if (timeFd == -1)
141 {
142 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500143 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
144 eno, "RC", timeFd);
Carol Wangef7abe12020-02-25 16:19:34 +0800145 throw std::system_error(eno, std::system_category());
146 }
147
148 // Starts the timer referred to by the file descriptor fd.
149 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
150 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
151 // as cancelable if the real-time clock undergoes a discontinuous change.
152 // In this way, we can monitor whether BMC time is changed or not.
153 auto r = timerfd_settime(
154 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
155 if (r != 0)
156 {
157 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500158 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
159 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800160 throw std::system_error(eno, std::system_category());
161 }
162
163 sd_event_source* es;
164 // Add a new I/O event source to an event loop. onTimeChange will be called
165 // when the event source is triggered.
166 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
167 if (r < 0)
168 {
169 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500170 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
171 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800172 throw std::system_error(eno, std::system_category());
173 }
174 timeChangeEventSource.reset(es);
175}
176
177ScheduledHostTransition::~ScheduledHostTransition()
178{
179 close(timeFd);
180}
181
182void ScheduledHostTransition::handleTimeUpdates()
183{
Carol Wang1dbbef42020-03-09 11:51:23 +0800184 // Stop the timer if it's running.
185 // Don't return directly when timer is stopped, because the timer is always
186 // disabled after the BMC is rebooted.
187 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800188 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800189 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800190 }
Carol Wangef7abe12020-02-25 16:19:34 +0800191
192 // Get scheduled time
193 auto schedTime = HostTransition::scheduledTime();
194
195 if (schedTime == 0)
196 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -0500197 debug(
198 "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
Carol Wangef7abe12020-02-25 16:19:34 +0800199 return;
200 }
201
202 auto deltaTime = seconds(schedTime) - getTime();
203 if (deltaTime <= seconds(0))
204 {
Andrew Geissler60a238d2024-01-03 07:29:45 -0600205 try
206 {
207 hostTransition();
208 }
209 catch (const sdbusplus::exception_t& e)
210 {
211 // If error indicates BMC is not at Ready error then reschedule for
212 // 60s later
213 if ((e.name() != nullptr) &&
214 (e.name() ==
215 std::string_view(
216 "xyz.openbmc_project.State.Host.Error.BMCNotReady")))
217 {
218 warning(
219 "BMC is not at ready, reschedule transition request for 60s");
220 timer.restart(seconds(60));
221 return;
222 }
223 else
224 {
225 throw;
226 }
227 }
Carol Wang1dbbef42020-03-09 11:51:23 +0800228 // Set scheduledTime to 0 to disable host transition and update
229 // scheduled values
230 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800231 }
232 else
233 {
234 // Start a timer to do host transition at scheduled time
235 timer.restart(deltaTime);
236 }
237}
238
239int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
240 uint32_t /* revents */,
241 void* userdata)
242{
243 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
244
245 std::array<char, 64> time{};
246
247 // We are not interested in the data here.
248 // So read until there is no new data here in the FD
249 while (read(fd, time.data(), time.max_size()) > 0)
250 ;
251
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500252 debug("BMC system time is changed");
Carol Wangef7abe12020-02-25 16:19:34 +0800253 schedHostTran->handleTimeUpdates();
254
255 return 0;
256}
257
Carol Wang1dbbef42020-03-09 11:51:23 +0800258void ScheduledHostTransition::serializeScheduledValues()
259{
260 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
261 std::ofstream os(path.c_str(), std::ios::binary);
262 cereal::JSONOutputArchive oarchive(os);
263
264 oarchive(HostTransition::scheduledTime(),
265 HostTransition::scheduledTransition());
266}
267
268bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
269 Transition& trans)
270{
271 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
272
273 try
274 {
275 if (fs::exists(path))
276 {
277 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
278 cereal::JSONInputArchive iarchive(is);
279 iarchive(time, trans);
280 return true;
281 }
282 }
Patrick Williams8583b3b2021-10-06 12:19:20 -0500283 catch (const std::exception& e)
Carol Wang1dbbef42020-03-09 11:51:23 +0800284 {
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500285 error("deserialize exception: {ERROR}", "ERROR", e);
Carol Wang1dbbef42020-03-09 11:51:23 +0800286 fs::remove(path);
287 }
288
289 return false;
290}
291
292void ScheduledHostTransition::restoreScheduledValues()
293{
294 uint64_t time;
295 Transition trans;
296 if (!deserializeScheduledValues(time, trans))
297 {
298 // set to default value
299 HostTransition::scheduledTime(0);
300 HostTransition::scheduledTransition(Transition::On);
301 }
302 else
303 {
304 HostTransition::scheduledTime(time);
305 HostTransition::scheduledTransition(trans);
306 // Rebooting BMC is something like the BMC time is changed,
307 // so go on with the same process as BMC time changed.
308 handleTimeUpdates();
309 }
310}
311
Carol Wang71230ef2020-02-18 17:39:49 +0800312} // namespace manager
313} // namespace state
314} // namespace phosphor