blob: c0fbe0b6786528c22ce67b1a10f89d56533e1220 [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
46uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
47{
Manojkiran Eda3ff5a362024-06-17 10:59:07 +053048 info("A scheduled host transition request has been made for {TIME}", "TIME",
Andrew Geissler60a238d2024-01-03 07:29:45 -060049 value);
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{
Amithash Prasasdf566c962024-07-22 20:28:42 -070094 auto hostName = std::string(HostState::namespace_path::host) +
95 std::to_string(id);
96 std::string hostPath =
97 sdbusplus::message::object_path(HostState::namespace_path::value) /
98 hostName;
Carol Wang6a5db3d2020-02-21 10:12:01 +080099
Andrew Geissler60a238d2024-01-03 07:29:45 -0600100 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
101
102 info("Trying to set requestedTransition to {REQUESTED_TRANSITION}",
103 "REQUESTED_TRANSITION", reqTrans);
104
Alexander Hansen95de31f2025-10-22 16:57:51 +0200105 utils::setProperty(bus, hostPath, HostState::interface,
106 HostState::property_names::requested_host_transition,
Andrew Geissler60a238d2024-01-03 07:29:45 -0600107 reqTrans);
108
Andrew Geissler68a8c312021-12-03 14:26:23 -0600109 // Set RestartCause to indicate this transition is occurring due to a
110 // scheduled host transition as long as it's not an off request
111 if (HostTransition::scheduledTransition() != HostState::Transition::Off)
112 {
113 info("Set RestartCause to scheduled power on reason");
114 auto resCause =
115 convertForMessage(HostState::RestartCause::ScheduledPowerOn);
Amithash Prasasdf566c962024-07-22 20:28:42 -0700116 utils::setProperty(bus, hostPath, HostState::interface,
Alexander Hansen95de31f2025-10-22 16:57:51 +0200117 HostState::property_names::restart_cause, resCause);
Andrew Geissler68a8c312021-12-03 14:26:23 -0600118 }
Carol Wang6a5db3d2020-02-21 10:12:01 +0800119}
120
121void ScheduledHostTransition::callback()
122{
123 // Stop timer, since we need to do host transition once only
124 timer.setEnabled(false);
125 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800126 // Set scheduledTime to 0 to disable host transition and update scheduled
127 // values
128 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800129}
130
Carol Wangef7abe12020-02-25 16:19:34 +0800131void ScheduledHostTransition::initialize()
132{
133 // Subscribe time change event
Manojkiran Eda3ff5a362024-06-17 10:59:07 +0530134 // Choose the MAX time that is possible to avoid misfires.
Carol Wangef7abe12020-02-25 16:19:34 +0800135 constexpr itimerspec maxTime = {
Andrew Geissler63f7b1a2023-07-24 14:54:58 -0400136 {0, 0}, // it_interval
137 {system_clock::duration::max().count(), 0}, // it_value
Carol Wangef7abe12020-02-25 16:19:34 +0800138 };
139
140 // Create and operate on a timer that delivers timer expiration
141 // notifications via a file descriptor.
142 timeFd = timerfd_create(CLOCK_REALTIME, 0);
143 if (timeFd == -1)
144 {
145 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500146 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
147 eno, "RC", timeFd);
Carol Wangef7abe12020-02-25 16:19:34 +0800148 throw std::system_error(eno, std::system_category());
149 }
150
151 // Starts the timer referred to by the file descriptor fd.
152 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
153 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
154 // as cancelable if the real-time clock undergoes a discontinuous change.
155 // In this way, we can monitor whether BMC time is changed or not.
156 auto r = timerfd_settime(
157 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
158 if (r != 0)
159 {
160 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500161 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
162 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800163 throw std::system_error(eno, std::system_category());
164 }
165
166 sd_event_source* es;
167 // Add a new I/O event source to an event loop. onTimeChange will be called
168 // when the event source is triggered.
169 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
170 if (r < 0)
171 {
172 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500173 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
174 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800175 throw std::system_error(eno, std::system_category());
176 }
177 timeChangeEventSource.reset(es);
178}
179
180ScheduledHostTransition::~ScheduledHostTransition()
181{
182 close(timeFd);
183}
184
185void ScheduledHostTransition::handleTimeUpdates()
186{
Carol Wang1dbbef42020-03-09 11:51:23 +0800187 // Stop the timer if it's running.
188 // Don't return directly when timer is stopped, because the timer is always
189 // disabled after the BMC is rebooted.
190 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800191 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800192 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800193 }
Carol Wangef7abe12020-02-25 16:19:34 +0800194
195 // Get scheduled time
196 auto schedTime = HostTransition::scheduledTime();
197
198 if (schedTime == 0)
199 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -0500200 debug(
201 "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
Carol Wangef7abe12020-02-25 16:19:34 +0800202 return;
203 }
204
205 auto deltaTime = seconds(schedTime) - getTime();
206 if (deltaTime <= seconds(0))
207 {
Andrew Geissler60a238d2024-01-03 07:29:45 -0600208 try
209 {
210 hostTransition();
211 }
212 catch (const sdbusplus::exception_t& e)
213 {
Patrick Williams9a286db2024-01-17 06:29:47 -0600214 using BMCNotReady = sdbusplus::error::xyz::openbmc_project::state::
215 host::BMCNotReady;
Andrew Geissler60a238d2024-01-03 07:29:45 -0600216 // If error indicates BMC is not at Ready error then reschedule for
217 // 60s later
218 if ((e.name() != nullptr) &&
Patrick Williams9a286db2024-01-17 06:29:47 -0600219 (e.name() == std::string_view(BMCNotReady::errName)))
Andrew Geissler60a238d2024-01-03 07:29:45 -0600220 {
221 warning(
222 "BMC is not at ready, reschedule transition request for 60s");
223 timer.restart(seconds(60));
224 return;
225 }
226 else
227 {
228 throw;
229 }
230 }
Carol Wang1dbbef42020-03-09 11:51:23 +0800231 // Set scheduledTime to 0 to disable host transition and update
232 // scheduled values
233 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800234 }
235 else
236 {
237 // Start a timer to do host transition at scheduled time
238 timer.restart(deltaTime);
239 }
240}
241
Patrick Williams1b2c3c02024-08-16 15:20:29 -0400242int ScheduledHostTransition::onTimeChange(
243 sd_event_source* /* es */, int fd, uint32_t /* revents */, void* userdata)
Carol Wangef7abe12020-02-25 16:19:34 +0800244{
Pavithra Barithaya319eda42024-06-21 11:54:43 -0500245 auto* schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
Carol Wangef7abe12020-02-25 16:19:34 +0800246
247 std::array<char, 64> time{};
248
249 // We are not interested in the data here.
250 // So read until there is no new data here in the FD
251 while (read(fd, time.data(), time.max_size()) > 0)
Pavithra Barithayab594ac12024-06-21 12:09:04 -0500252 {}
Carol Wangef7abe12020-02-25 16:19:34 +0800253
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500254 debug("BMC system time is changed");
Carol Wangef7abe12020-02-25 16:19:34 +0800255 schedHostTran->handleTimeUpdates();
256
257 return 0;
258}
259
Carol Wang1dbbef42020-03-09 11:51:23 +0800260void ScheduledHostTransition::serializeScheduledValues()
261{
262 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
263 std::ofstream os(path.c_str(), std::ios::binary);
264 cereal::JSONOutputArchive oarchive(os);
265
266 oarchive(HostTransition::scheduledTime(),
267 HostTransition::scheduledTransition());
268}
269
270bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
271 Transition& trans)
272{
273 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
274
275 try
276 {
277 if (fs::exists(path))
278 {
279 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
280 cereal::JSONInputArchive iarchive(is);
281 iarchive(time, trans);
282 return true;
283 }
284 }
Patrick Williams8583b3b2021-10-06 12:19:20 -0500285 catch (const std::exception& e)
Carol Wang1dbbef42020-03-09 11:51:23 +0800286 {
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500287 error("deserialize exception: {ERROR}", "ERROR", e);
Carol Wang1dbbef42020-03-09 11:51:23 +0800288 fs::remove(path);
289 }
290
291 return false;
292}
293
294void ScheduledHostTransition::restoreScheduledValues()
295{
296 uint64_t time;
297 Transition trans;
298 if (!deserializeScheduledValues(time, trans))
299 {
300 // set to default value
301 HostTransition::scheduledTime(0);
302 HostTransition::scheduledTransition(Transition::On);
303 }
304 else
305 {
306 HostTransition::scheduledTime(time);
307 HostTransition::scheduledTransition(trans);
308 // Rebooting BMC is something like the BMC time is changed,
309 // so go on with the same process as BMC time changed.
310 handleTimeUpdates();
311 }
312}
313
Carol Wang71230ef2020-02-18 17:39:49 +0800314} // namespace manager
315} // namespace state
316} // namespace phosphor