blob: f7c04a0fa43c85e003b7f53b190d258ac0fec2f6 [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 =
42 sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
Andrew Geissler68a8c312021-12-03 14:26:23 -060043using HostState = sdbusplus::xyz::openbmc_project::State::server::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{
Carol Wang4ca6f3f2020-02-19 16:28:59 +080050 if (value == 0)
51 {
52 // 0 means the function Scheduled Host Transition is disabled
Carol Wang6a5db3d2020-02-21 10:12:01 +080053 // Stop the timer if it's running
54 if (timer.isEnabled())
55 {
56 timer.setEnabled(false);
Andrew Geisslerad65b2d2021-09-21 12:53:29 -050057 debug(
58 "scheduledTime: The function Scheduled Host Transition is disabled.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080059 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080060 }
61 else
62 {
Carol Wang6a5db3d2020-02-21 10:12:01 +080063 auto deltaTime = seconds(value) - getTime();
64 if (deltaTime < seconds(0))
65 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -050066 error(
67 "Scheduled time is earlier than current time. Fail to schedule host transition.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080068 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 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080076 }
Carol Wang6a5db3d2020-02-21 10:12:01 +080077
Carol Wang1dbbef42020-03-09 11:51:23 +080078 // Set scheduledTime
79 HostTransition::scheduledTime(value);
80 // Store scheduled values
81 serializeScheduledValues();
82
83 return value;
Carol Wang71230ef2020-02-18 17:39:49 +080084}
85
Carol Wang4ca6f3f2020-02-19 16:28:59 +080086seconds ScheduledHostTransition::getTime()
87{
88 auto now = system_clock::now();
89 return duration_cast<seconds>(now.time_since_epoch());
90}
91
Carol Wang6a5db3d2020-02-21 10:12:01 +080092void ScheduledHostTransition::hostTransition()
93{
Patrick Williams211d9722022-04-07 21:24:33 -050094 auto hostPath = std::string{HOST_OBJPATH} + std::to_string(id);
Carol Wang6a5db3d2020-02-21 10:12:01 +080095
Andrew Geissler68a8c312021-12-03 14:26:23 -060096 // Set RestartCause to indicate this transition is occurring due to a
97 // scheduled host transition as long as it's not an off request
98 if (HostTransition::scheduledTransition() != HostState::Transition::Off)
99 {
100 info("Set RestartCause to scheduled power on reason");
101 auto resCause =
102 convertForMessage(HostState::RestartCause::ScheduledPowerOn);
103 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE,
104 resCause);
105 }
Carol Wang6a5db3d2020-02-21 10:12:01 +0800106 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
107
Carol Wangdc059392020-03-13 17:39:17 +0800108 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
109 reqTrans);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800110
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500111 info("Set requestedTransition to {REQUESTED_TRANSITION}",
112 "REQUESTED_TRANSITION", reqTrans);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800113}
114
115void ScheduledHostTransition::callback()
116{
117 // Stop timer, since we need to do host transition once only
118 timer.setEnabled(false);
119 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800120 // Set scheduledTime to 0 to disable host transition and update scheduled
121 // values
122 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800123}
124
Carol Wangef7abe12020-02-25 16:19:34 +0800125void ScheduledHostTransition::initialize()
126{
127 // Subscribe time change event
128 // Choose the MAX time that is possible to avoid mis fires.
129 constexpr itimerspec maxTime = {
Andrew Geissler63f7b1a2023-07-24 14:54:58 -0400130 {0, 0}, // it_interval
131 {system_clock::duration::max().count(), 0}, // it_value
Carol Wangef7abe12020-02-25 16:19:34 +0800132 };
133
134 // Create and operate on a timer that delivers timer expiration
135 // notifications via a file descriptor.
136 timeFd = timerfd_create(CLOCK_REALTIME, 0);
137 if (timeFd == -1)
138 {
139 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500140 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
141 eno, "RC", timeFd);
Carol Wangef7abe12020-02-25 16:19:34 +0800142 throw std::system_error(eno, std::system_category());
143 }
144
145 // Starts the timer referred to by the file descriptor fd.
146 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
147 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
148 // as cancelable if the real-time clock undergoes a discontinuous change.
149 // In this way, we can monitor whether BMC time is changed or not.
150 auto r = timerfd_settime(
151 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
152 if (r != 0)
153 {
154 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500155 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
156 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800157 throw std::system_error(eno, std::system_category());
158 }
159
160 sd_event_source* es;
161 // Add a new I/O event source to an event loop. onTimeChange will be called
162 // when the event source is triggered.
163 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
164 if (r < 0)
165 {
166 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500167 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
168 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800169 throw std::system_error(eno, std::system_category());
170 }
171 timeChangeEventSource.reset(es);
172}
173
174ScheduledHostTransition::~ScheduledHostTransition()
175{
176 close(timeFd);
177}
178
179void ScheduledHostTransition::handleTimeUpdates()
180{
Carol Wang1dbbef42020-03-09 11:51:23 +0800181 // Stop the timer if it's running.
182 // Don't return directly when timer is stopped, because the timer is always
183 // disabled after the BMC is rebooted.
184 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800185 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800186 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800187 }
Carol Wangef7abe12020-02-25 16:19:34 +0800188
189 // Get scheduled time
190 auto schedTime = HostTransition::scheduledTime();
191
192 if (schedTime == 0)
193 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -0500194 debug(
195 "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
Carol Wangef7abe12020-02-25 16:19:34 +0800196 return;
197 }
198
199 auto deltaTime = seconds(schedTime) - getTime();
200 if (deltaTime <= seconds(0))
201 {
Carol Wangef7abe12020-02-25 16:19:34 +0800202 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800203 // Set scheduledTime to 0 to disable host transition and update
204 // scheduled values
205 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800206 }
207 else
208 {
209 // Start a timer to do host transition at scheduled time
210 timer.restart(deltaTime);
211 }
212}
213
214int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
215 uint32_t /* revents */,
216 void* userdata)
217{
218 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
219
220 std::array<char, 64> time{};
221
222 // We are not interested in the data here.
223 // So read until there is no new data here in the FD
224 while (read(fd, time.data(), time.max_size()) > 0)
225 ;
226
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500227 debug("BMC system time is changed");
Carol Wangef7abe12020-02-25 16:19:34 +0800228 schedHostTran->handleTimeUpdates();
229
230 return 0;
231}
232
Carol Wang1dbbef42020-03-09 11:51:23 +0800233void ScheduledHostTransition::serializeScheduledValues()
234{
235 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
236 std::ofstream os(path.c_str(), std::ios::binary);
237 cereal::JSONOutputArchive oarchive(os);
238
239 oarchive(HostTransition::scheduledTime(),
240 HostTransition::scheduledTransition());
241}
242
243bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
244 Transition& trans)
245{
246 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
247
248 try
249 {
250 if (fs::exists(path))
251 {
252 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
253 cereal::JSONInputArchive iarchive(is);
254 iarchive(time, trans);
255 return true;
256 }
257 }
Patrick Williams8583b3b2021-10-06 12:19:20 -0500258 catch (const std::exception& e)
Carol Wang1dbbef42020-03-09 11:51:23 +0800259 {
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500260 error("deserialize exception: {ERROR}", "ERROR", e);
Carol Wang1dbbef42020-03-09 11:51:23 +0800261 fs::remove(path);
262 }
263
264 return false;
265}
266
267void ScheduledHostTransition::restoreScheduledValues()
268{
269 uint64_t time;
270 Transition trans;
271 if (!deserializeScheduledValues(time, trans))
272 {
273 // set to default value
274 HostTransition::scheduledTime(0);
275 HostTransition::scheduledTransition(Transition::On);
276 }
277 else
278 {
279 HostTransition::scheduledTime(time);
280 HostTransition::scheduledTransition(trans);
281 // Rebooting BMC is something like the BMC time is changed,
282 // so go on with the same process as BMC time changed.
283 handleTimeUpdates();
284 }
285}
286
Carol Wang71230ef2020-02-18 17:39:49 +0800287} // namespace manager
288} // namespace state
289} // namespace phosphor