blob: 06035aa77487beb724021b1430abe1849dbfd8e5 [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"
Carol Wang71230ef2020-02-18 17:39:49 +08004
Andrew Geisslere426b582020-05-28 12:40:55 -05005#include <sys/timerfd.h>
6#include <unistd.h>
7
8#include <cereal/archives/json.hpp>
Carol Wang4ca6f3f2020-02-19 16:28:59 +08009#include <phosphor-logging/elog-errors.hpp>
10#include <phosphor-logging/elog.hpp>
Andrew Geissler8ffdb262021-09-20 15:25:19 -050011#include <phosphor-logging/lg2.hpp>
Carol Wang4ca6f3f2020-02-19 16:28:59 +080012#include <xyz/openbmc_project/ScheduledTime/error.hpp>
Andrew Geisslere426b582020-05-28 12:40:55 -050013
Carol Wang4ca6f3f2020-02-19 16:28:59 +080014#include <chrono>
Carol Wang1dbbef42020-03-09 11:51:23 +080015#include <filesystem>
16#include <fstream>
Carol Wangef7abe12020-02-25 16:19:34 +080017
18// Need to do this since its not exported outside of the kernel.
19// Refer : https://gist.github.com/lethean/446cea944b7441228298
20#ifndef TFD_TIMER_CANCEL_ON_SET
21#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
22#endif
23
24// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
25#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
Carol Wang4ca6f3f2020-02-19 16:28:59 +080026
Carol Wang71230ef2020-02-18 17:39:49 +080027namespace phosphor
28{
29namespace state
30{
31namespace manager
32{
33
Andrew Geissler8ffdb262021-09-20 15:25:19 -050034PHOSPHOR_LOG2_USING;
35
Carol Wang1dbbef42020-03-09 11:51:23 +080036namespace fs = std::filesystem;
37
Carol Wang4ca6f3f2020-02-19 16:28:59 +080038using namespace std::chrono;
39using namespace phosphor::logging;
40using namespace xyz::openbmc_project::ScheduledTime;
41using InvalidTimeError =
42 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
Carol Wang71230ef2020-02-18 17:39:49 +080043using HostTransition =
44 sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
45
Carol Wang6a5db3d2020-02-21 10:12:01 +080046constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
47
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{
94 auto hostPath = std::string{HOST_OBJPATH} + '0';
95
96 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
97
Carol Wangdc059392020-03-13 17:39:17 +080098 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
99 reqTrans);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800100
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500101 info("Set requestedTransition to {REQUESTED_TRANSITION}",
102 "REQUESTED_TRANSITION", reqTrans);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800103}
104
105void ScheduledHostTransition::callback()
106{
107 // Stop timer, since we need to do host transition once only
108 timer.setEnabled(false);
109 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800110 // Set scheduledTime to 0 to disable host transition and update scheduled
111 // values
112 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800113}
114
Carol Wangef7abe12020-02-25 16:19:34 +0800115void ScheduledHostTransition::initialize()
116{
117 // Subscribe time change event
118 // Choose the MAX time that is possible to avoid mis fires.
119 constexpr itimerspec maxTime = {
120 {0, 0}, // it_interval
121 {TIME_T_MAX, 0}, // it_value
122 };
123
124 // Create and operate on a timer that delivers timer expiration
125 // notifications via a file descriptor.
126 timeFd = timerfd_create(CLOCK_REALTIME, 0);
127 if (timeFd == -1)
128 {
129 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500130 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
131 eno, "RC", timeFd);
Carol Wangef7abe12020-02-25 16:19:34 +0800132 throw std::system_error(eno, std::system_category());
133 }
134
135 // Starts the timer referred to by the file descriptor fd.
136 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
137 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
138 // as cancelable if the real-time clock undergoes a discontinuous change.
139 // In this way, we can monitor whether BMC time is changed or not.
140 auto r = timerfd_settime(
141 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
142 if (r != 0)
143 {
144 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500145 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
146 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800147 throw std::system_error(eno, std::system_category());
148 }
149
150 sd_event_source* es;
151 // Add a new I/O event source to an event loop. onTimeChange will be called
152 // when the event source is triggered.
153 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
154 if (r < 0)
155 {
156 auto eno = errno;
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500157 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
158 "RC", r);
Carol Wangef7abe12020-02-25 16:19:34 +0800159 throw std::system_error(eno, std::system_category());
160 }
161 timeChangeEventSource.reset(es);
162}
163
164ScheduledHostTransition::~ScheduledHostTransition()
165{
166 close(timeFd);
167}
168
169void ScheduledHostTransition::handleTimeUpdates()
170{
Carol Wang1dbbef42020-03-09 11:51:23 +0800171 // Stop the timer if it's running.
172 // Don't return directly when timer is stopped, because the timer is always
173 // disabled after the BMC is rebooted.
174 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800175 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800176 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800177 }
Carol Wangef7abe12020-02-25 16:19:34 +0800178
179 // Get scheduled time
180 auto schedTime = HostTransition::scheduledTime();
181
182 if (schedTime == 0)
183 {
Andrew Geisslerad65b2d2021-09-21 12:53:29 -0500184 debug(
185 "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
Carol Wangef7abe12020-02-25 16:19:34 +0800186 return;
187 }
188
189 auto deltaTime = seconds(schedTime) - getTime();
190 if (deltaTime <= seconds(0))
191 {
Carol Wangef7abe12020-02-25 16:19:34 +0800192 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800193 // Set scheduledTime to 0 to disable host transition and update
194 // scheduled values
195 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800196 }
197 else
198 {
199 // Start a timer to do host transition at scheduled time
200 timer.restart(deltaTime);
201 }
202}
203
204int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
205 uint32_t /* revents */,
206 void* userdata)
207{
208 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
209
210 std::array<char, 64> time{};
211
212 // We are not interested in the data here.
213 // So read until there is no new data here in the FD
214 while (read(fd, time.data(), time.max_size()) > 0)
215 ;
216
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500217 debug("BMC system time is changed");
Carol Wangef7abe12020-02-25 16:19:34 +0800218 schedHostTran->handleTimeUpdates();
219
220 return 0;
221}
222
Carol Wang1dbbef42020-03-09 11:51:23 +0800223void ScheduledHostTransition::serializeScheduledValues()
224{
225 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
226 std::ofstream os(path.c_str(), std::ios::binary);
227 cereal::JSONOutputArchive oarchive(os);
228
229 oarchive(HostTransition::scheduledTime(),
230 HostTransition::scheduledTransition());
231}
232
233bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
234 Transition& trans)
235{
236 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
237
238 try
239 {
240 if (fs::exists(path))
241 {
242 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
243 cereal::JSONInputArchive iarchive(is);
244 iarchive(time, trans);
245 return true;
246 }
247 }
Patrick Williams8583b3b2021-10-06 12:19:20 -0500248 catch (const std::exception& e)
Carol Wang1dbbef42020-03-09 11:51:23 +0800249 {
Andrew Geissler8ffdb262021-09-20 15:25:19 -0500250 error("deserialize exception: {ERROR}", "ERROR", e);
Carol Wang1dbbef42020-03-09 11:51:23 +0800251 fs::remove(path);
252 }
253
254 return false;
255}
256
257void ScheduledHostTransition::restoreScheduledValues()
258{
259 uint64_t time;
260 Transition trans;
261 if (!deserializeScheduledValues(time, trans))
262 {
263 // set to default value
264 HostTransition::scheduledTime(0);
265 HostTransition::scheduledTransition(Transition::On);
266 }
267 else
268 {
269 HostTransition::scheduledTime(time);
270 HostTransition::scheduledTransition(trans);
271 // Rebooting BMC is something like the BMC time is changed,
272 // so go on with the same process as BMC time changed.
273 handleTimeUpdates();
274 }
275}
276
Carol Wang71230ef2020-02-18 17:39:49 +0800277} // namespace manager
278} // namespace state
279} // namespace phosphor