blob: 6f9edec7fa1efba1751ab5d516178c71b0a476d1 [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>
Patrick Williams9a286db2024-01-17 06:29:47 -060014#include <xyz/openbmc_project/State/Host/error.hpp>
Andrew Geisslere426b582020-05-28 12:40:55 -050015
Carol Wang4ca6f3f2020-02-19 16:28:59 +080016#include <chrono>
Carol Wang1dbbef42020-03-09 11:51:23 +080017#include <filesystem>
18#include <fstream>
Carol Wangef7abe12020-02-25 16:19:34 +080019
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
Carol Wang71230ef2020-02-18 17:39:49 +080026namespace phosphor
27{
28namespace state
29{
30namespace manager
31{
32
Andrew Geissler8ffdb262021-09-20 15:25:19 -050033PHOSPHOR_LOG2_USING;
34
Carol Wang1dbbef42020-03-09 11:51:23 +080035namespace fs = std::filesystem;
36
Carol Wang4ca6f3f2020-02-19 16:28:59 +080037using namespace std::chrono;
38using namespace phosphor::logging;
39using namespace xyz::openbmc_project::ScheduledTime;
40using InvalidTimeError =
41 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
Carol Wang71230ef2020-02-18 17:39:49 +080042using HostTransition =
Patrick Williams7e969cb2023-08-23 16:24:23 -050043 sdbusplus::server::xyz::openbmc_project::state::ScheduledHostTransition;
44using HostState = sdbusplus::server::xyz::openbmc_project::state::Host;
Carol Wang71230ef2020-02-18 17:39:49 +080045
Carol Wang6a5db3d2020-02-21 10:12:01 +080046constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
Andrew Geissler68a8c312021-12-03 14:26:23 -060047constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause";
Carol Wang6a5db3d2020-02-21 10:12:01 +080048
Carol Wang71230ef2020-02-18 17:39:49 +080049uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
50{
Manojkiran Eda3ff5a362024-06-17 10:59:07 +053051 info("A scheduled host transition request has been made for {TIME}", "TIME",
Andrew Geissler60a238d2024-01-03 07:29:45 -060052 value);
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{
Patrick Williams211d9722022-04-07 21:24:33 -050097 auto hostPath = std::string{HOST_OBJPATH} + std::to_string(id);
Carol Wang6a5db3d2020-02-21 10:12:01 +080098
Andrew Geissler60a238d2024-01-03 07:29:45 -060099 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
100
101 info("Trying to set requestedTransition to {REQUESTED_TRANSITION}",
102 "REQUESTED_TRANSITION", reqTrans);
103
104 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
105 reqTrans);
106
Andrew Geissler68a8c312021-12-03 14:26:23 -0600107 // Set RestartCause to indicate this transition is occurring due to a
108 // scheduled host transition as long as it's not an off request
109 if (HostTransition::scheduledTransition() != HostState::Transition::Off)
110 {
111 info("Set RestartCause to scheduled power on reason");
112 auto resCause =
113 convertForMessage(HostState::RestartCause::ScheduledPowerOn);
114 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE,
115 resCause);
116 }
Carol Wang6a5db3d2020-02-21 10:12:01 +0800117}
118
119void ScheduledHostTransition::callback()
120{
121 // Stop timer, since we need to do host transition once only
122 timer.setEnabled(false);
123 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800124 // Set scheduledTime to 0 to disable host transition and update scheduled
125 // values
126 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800127}
128
Carol Wangef7abe12020-02-25 16:19:34 +0800129void ScheduledHostTransition::initialize()
130{
131 // Subscribe time change event
Manojkiran Eda3ff5a362024-06-17 10:59:07 +0530132 // Choose the MAX time that is possible to avoid misfires.
Carol Wangef7abe12020-02-25 16:19:34 +0800133 constexpr itimerspec maxTime = {
Andrew Geissler63f7b1a2023-07-24 14:54:58 -0400134 {0, 0}, // it_interval
135 {system_clock::duration::max().count(), 0}, // it_value
Carol Wangef7abe12020-02-25 16:19:34 +0800136 };
137
138 // Create and operate on a timer that delivers timer expiration
139 // notifications via a file descriptor.
140 timeFd = timerfd_create(CLOCK_REALTIME, 0);
141 if (timeFd == -1)
142 {
143 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500144 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
145 eno, "RC", timeFd);
Carol Wangef7abe12020-02-25 16:19:34 +0800146 throw std::system_error(eno, std::system_category());
147 }
148
149 // Starts the timer referred to by the file descriptor fd.
150 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
151 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
152 // as cancelable if the real-time clock undergoes a discontinuous change.
153 // In this way, we can monitor whether BMC time is changed or not.
154 auto r = timerfd_settime(
155 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
156 if (r != 0)
157 {
158 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500159 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
160 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800161 throw std::system_error(eno, std::system_category());
162 }
163
164 sd_event_source* es;
165 // Add a new I/O event source to an event loop. onTimeChange will be called
166 // when the event source is triggered.
167 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
168 if (r < 0)
169 {
170 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500171 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
172 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800173 throw std::system_error(eno, std::system_category());
174 }
175 timeChangeEventSource.reset(es);
176}
177
178ScheduledHostTransition::~ScheduledHostTransition()
179{
180 close(timeFd);
181}
182
183void ScheduledHostTransition::handleTimeUpdates()
184{
Carol Wang1dbbef42020-03-09 11:51:23 +0800185 // Stop the timer if it's running.
186 // Don't return directly when timer is stopped, because the timer is always
187 // disabled after the BMC is rebooted.
188 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800189 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800190 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800191 }
Carol Wangef7abe12020-02-25 16:19:34 +0800192
193 // Get scheduled time
194 auto schedTime = HostTransition::scheduledTime();
195
196 if (schedTime == 0)
197 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -0500198 debug(
199 "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
Carol Wangef7abe12020-02-25 16:19:34 +0800200 return;
201 }
202
203 auto deltaTime = seconds(schedTime) - getTime();
204 if (deltaTime <= seconds(0))
205 {
Andrew Geissler60a238d2024-01-03 07:29:45 -0600206 try
207 {
208 hostTransition();
209 }
210 catch (const sdbusplus::exception_t& e)
211 {
Patrick Williams9a286db2024-01-17 06:29:47 -0600212 using BMCNotReady = sdbusplus::error::xyz::openbmc_project::state::
213 host::BMCNotReady;
Andrew Geissler60a238d2024-01-03 07:29:45 -0600214 // If error indicates BMC is not at Ready error then reschedule for
215 // 60s later
216 if ((e.name() != nullptr) &&
Patrick Williams9a286db2024-01-17 06:29:47 -0600217 (e.name() == std::string_view(BMCNotReady::errName)))
Andrew Geissler60a238d2024-01-03 07:29:45 -0600218 {
219 warning(
220 "BMC is not at ready, reschedule transition request for 60s");
221 timer.restart(seconds(60));
222 return;
223 }
224 else
225 {
226 throw;
227 }
228 }
Carol Wang1dbbef42020-03-09 11:51:23 +0800229 // Set scheduledTime to 0 to disable host transition and update
230 // scheduled values
231 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800232 }
233 else
234 {
235 // Start a timer to do host transition at scheduled time
236 timer.restart(deltaTime);
237 }
238}
239
240int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
241 uint32_t /* revents */,
242 void* userdata)
243{
244 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
245
246 std::array<char, 64> time{};
247
248 // We are not interested in the data here.
249 // So read until there is no new data here in the FD
250 while (read(fd, time.data(), time.max_size()) > 0)
251 ;
252
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500253 debug("BMC system time is changed");
Carol Wangef7abe12020-02-25 16:19:34 +0800254 schedHostTran->handleTimeUpdates();
255
256 return 0;
257}
258
Carol Wang1dbbef42020-03-09 11:51:23 +0800259void ScheduledHostTransition::serializeScheduledValues()
260{
261 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
262 std::ofstream os(path.c_str(), std::ios::binary);
263 cereal::JSONOutputArchive oarchive(os);
264
265 oarchive(HostTransition::scheduledTime(),
266 HostTransition::scheduledTransition());
267}
268
269bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
270 Transition& trans)
271{
272 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
273
274 try
275 {
276 if (fs::exists(path))
277 {
278 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
279 cereal::JSONInputArchive iarchive(is);
280 iarchive(time, trans);
281 return true;
282 }
283 }
Patrick Williams8583b3b2021-10-06 12:19:20 -0500284 catch (const std::exception& e)
Carol Wang1dbbef42020-03-09 11:51:23 +0800285 {
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500286 error("deserialize exception: {ERROR}", "ERROR", e);
Carol Wang1dbbef42020-03-09 11:51:23 +0800287 fs::remove(path);
288 }
289
290 return false;
291}
292
293void ScheduledHostTransition::restoreScheduledValues()
294{
295 uint64_t time;
296 Transition trans;
297 if (!deserializeScheduledValues(time, trans))
298 {
299 // set to default value
300 HostTransition::scheduledTime(0);
301 HostTransition::scheduledTransition(Transition::On);
302 }
303 else
304 {
305 HostTransition::scheduledTime(time);
306 HostTransition::scheduledTransition(trans);
307 // Rebooting BMC is something like the BMC time is changed,
308 // so go on with the same process as BMC time changed.
309 handleTimeUpdates();
310 }
311}
312
Carol Wang71230ef2020-02-18 17:39:49 +0800313} // namespace manager
314} // namespace state
315} // namespace phosphor