blob: ed843cd1941806efc141ee20a701135b9a73cf9b [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
25// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
26#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
Carol Wang4ca6f3f2020-02-19 16:28:59 +080027
Carol Wang71230ef2020-02-18 17:39:49 +080028namespace phosphor
29{
30namespace state
31{
32namespace manager
33{
34
Andrew Geissler8ffdb262021-09-20 15:25:19 -050035PHOSPHOR_LOG2_USING;
36
Carol Wang1dbbef42020-03-09 11:51:23 +080037namespace fs = std::filesystem;
38
Carol Wang4ca6f3f2020-02-19 16:28:59 +080039using namespace std::chrono;
40using namespace phosphor::logging;
41using namespace xyz::openbmc_project::ScheduledTime;
42using InvalidTimeError =
43 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
Carol Wang71230ef2020-02-18 17:39:49 +080044using HostTransition =
45 sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
Andrew Geissler68a8c312021-12-03 14:26:23 -060046using HostState = sdbusplus::xyz::openbmc_project::State::server::Host;
Carol Wang71230ef2020-02-18 17:39:49 +080047
Carol Wang6a5db3d2020-02-21 10:12:01 +080048constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
Andrew Geissler68a8c312021-12-03 14:26:23 -060049constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause";
Carol Wang6a5db3d2020-02-21 10:12:01 +080050
Carol Wang71230ef2020-02-18 17:39:49 +080051uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
52{
Carol Wang4ca6f3f2020-02-19 16:28:59 +080053 if (value == 0)
54 {
55 // 0 means the function Scheduled Host Transition is disabled
Carol Wang6a5db3d2020-02-21 10:12:01 +080056 // Stop the timer if it's running
57 if (timer.isEnabled())
58 {
59 timer.setEnabled(false);
Andrew Geisslerad65b2d2021-09-21 12:53:29 -050060 debug(
61 "scheduledTime: The function Scheduled Host Transition is disabled.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080062 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080063 }
64 else
65 {
Carol Wang6a5db3d2020-02-21 10:12:01 +080066 auto deltaTime = seconds(value) - getTime();
67 if (deltaTime < seconds(0))
68 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -050069 error(
70 "Scheduled time is earlier than current time. Fail to schedule host transition.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080071 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 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080079 }
Carol Wang6a5db3d2020-02-21 10:12:01 +080080
Carol Wang1dbbef42020-03-09 11:51:23 +080081 // Set scheduledTime
82 HostTransition::scheduledTime(value);
83 // Store scheduled values
84 serializeScheduledValues();
85
86 return value;
Carol Wang71230ef2020-02-18 17:39:49 +080087}
88
Carol Wang4ca6f3f2020-02-19 16:28:59 +080089seconds ScheduledHostTransition::getTime()
90{
91 auto now = system_clock::now();
92 return duration_cast<seconds>(now.time_since_epoch());
93}
94
Carol Wang6a5db3d2020-02-21 10:12:01 +080095void ScheduledHostTransition::hostTransition()
96{
97 auto hostPath = std::string{HOST_OBJPATH} + '0';
98
Andrew Geissler68a8c312021-12-03 14:26:23 -060099 // Set RestartCause to indicate this transition is occurring due to a
100 // scheduled host transition as long as it's not an off request
101 if (HostTransition::scheduledTransition() != HostState::Transition::Off)
102 {
103 info("Set RestartCause to scheduled power on reason");
104 auto resCause =
105 convertForMessage(HostState::RestartCause::ScheduledPowerOn);
106 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE,
107 resCause);
108 }
Carol Wang6a5db3d2020-02-21 10:12:01 +0800109 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
110
Carol Wangdc059392020-03-13 17:39:17 +0800111 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
112 reqTrans);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800113
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500114 info("Set requestedTransition to {REQUESTED_TRANSITION}",
115 "REQUESTED_TRANSITION", reqTrans);
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 = {
133 {0, 0}, // it_interval
134 {TIME_T_MAX, 0}, // it_value
135 };
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 {
Carol Wangef7abe12020-02-25 16:19:34 +0800205 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800206 // Set scheduledTime to 0 to disable host transition and update
207 // scheduled values
208 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800209 }
210 else
211 {
212 // Start a timer to do host transition at scheduled time
213 timer.restart(deltaTime);
214 }
215}
216
217int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
218 uint32_t /* revents */,
219 void* userdata)
220{
221 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
222
223 std::array<char, 64> time{};
224
225 // We are not interested in the data here.
226 // So read until there is no new data here in the FD
227 while (read(fd, time.data(), time.max_size()) > 0)
228 ;
229
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500230 debug("BMC system time is changed");
Carol Wangef7abe12020-02-25 16:19:34 +0800231 schedHostTran->handleTimeUpdates();
232
233 return 0;
234}
235
Carol Wang1dbbef42020-03-09 11:51:23 +0800236void ScheduledHostTransition::serializeScheduledValues()
237{
238 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
239 std::ofstream os(path.c_str(), std::ios::binary);
240 cereal::JSONOutputArchive oarchive(os);
241
242 oarchive(HostTransition::scheduledTime(),
243 HostTransition::scheduledTransition());
244}
245
246bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
247 Transition& trans)
248{
249 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
250
251 try
252 {
253 if (fs::exists(path))
254 {
255 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
256 cereal::JSONInputArchive iarchive(is);
257 iarchive(time, trans);
258 return true;
259 }
260 }
Patrick Williams8583b3b2021-10-06 12:19:20 -0500261 catch (const std::exception& e)
Carol Wang1dbbef42020-03-09 11:51:23 +0800262 {
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500263 error("deserialize exception: {ERROR}", "ERROR", e);
Carol Wang1dbbef42020-03-09 11:51:23 +0800264 fs::remove(path);
265 }
266
267 return false;
268}
269
270void ScheduledHostTransition::restoreScheduledValues()
271{
272 uint64_t time;
273 Transition trans;
274 if (!deserializeScheduledValues(time, trans))
275 {
276 // set to default value
277 HostTransition::scheduledTime(0);
278 HostTransition::scheduledTransition(Transition::On);
279 }
280 else
281 {
282 HostTransition::scheduledTime(time);
283 HostTransition::scheduledTransition(trans);
284 // Rebooting BMC is something like the BMC time is changed,
285 // so go on with the same process as BMC time changed.
286 handleTimeUpdates();
287 }
288}
289
Carol Wang71230ef2020-02-18 17:39:49 +0800290} // namespace manager
291} // namespace state
292} // namespace phosphor