blob: 6ff28ac294875e8d2e6879da27a6c06bb9edcfc5 [file] [log] [blame]
Carol Wang71230ef2020-02-18 17:39:49 +08001#include "scheduled_host_transition.hpp"
2
Carol Wang4ca6f3f2020-02-19 16:28:59 +08003#include <phosphor-logging/elog-errors.hpp>
4#include <phosphor-logging/elog.hpp>
5#include <phosphor-logging/log.hpp>
6#include <xyz/openbmc_project/ScheduledTime/error.hpp>
Carol Wang1dbbef42020-03-09 11:51:23 +08007#include <cereal/archives/json.hpp>
Carol Wang4ca6f3f2020-02-19 16:28:59 +08008#include <chrono>
Carol Wang1dbbef42020-03-09 11:51:23 +08009#include <filesystem>
10#include <fstream>
Carol Wangef7abe12020-02-25 16:19:34 +080011#include <sys/timerfd.h>
12#include <unistd.h>
13
14// Need to do this since its not exported outside of the kernel.
15// Refer : https://gist.github.com/lethean/446cea944b7441228298
16#ifndef TFD_TIMER_CANCEL_ON_SET
17#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
18#endif
19
20// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
21#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
Carol Wang4ca6f3f2020-02-19 16:28:59 +080022
Carol Wang71230ef2020-02-18 17:39:49 +080023namespace phosphor
24{
25namespace state
26{
27namespace manager
28{
29
Carol Wang1dbbef42020-03-09 11:51:23 +080030namespace fs = std::filesystem;
31
Carol Wang4ca6f3f2020-02-19 16:28:59 +080032using namespace std::chrono;
33using namespace phosphor::logging;
34using namespace xyz::openbmc_project::ScheduledTime;
Carol Wang6a5db3d2020-02-21 10:12:01 +080035using sdbusplus::exception::SdBusError;
Carol Wang4ca6f3f2020-02-19 16:28:59 +080036using InvalidTimeError =
37 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
Carol Wang71230ef2020-02-18 17:39:49 +080038using HostTransition =
39 sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
40
Carol Wang6a5db3d2020-02-21 10:12:01 +080041constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
42constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
43constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
44constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
45constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
46
Carol Wang71230ef2020-02-18 17:39:49 +080047uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
48{
Carol Wang4ca6f3f2020-02-19 16:28:59 +080049 if (value == 0)
50 {
51 // 0 means the function Scheduled Host Transition is disabled
Carol Wang6a5db3d2020-02-21 10:12:01 +080052 // Stop the timer if it's running
53 if (timer.isEnabled())
54 {
55 timer.setEnabled(false);
Carol Wang1dbbef42020-03-09 11:51:23 +080056 log<level::INFO>("scheduledTime: The function Scheduled Host "
57 "Transition is disabled.");
Carol Wang6a5db3d2020-02-21 10:12:01 +080058 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080059 }
60 else
61 {
Carol Wang6a5db3d2020-02-21 10:12:01 +080062 auto deltaTime = seconds(value) - getTime();
63 if (deltaTime < seconds(0))
64 {
65 log<level::ERR>(
66 "Scheduled time is earlier than current time. Fail to "
67 "schedule host transition.");
68 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 +080092std::string getService(sdbusplus::bus::bus& bus, std::string path,
93 std::string interface)
94{
95 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
96 MAPPER_INTERFACE, "GetObject");
97
98 mapper.append(path, std::vector<std::string>({interface}));
99
100 std::vector<std::pair<std::string, std::vector<std::string>>>
101 mapperResponse;
102
103 try
104 {
105 auto mapperResponseMsg = bus.call(mapper);
106
107 mapperResponseMsg.read(mapperResponse);
108 if (mapperResponse.empty())
109 {
110 log<level::ERR>("Error no matching service",
111 entry("PATH=%s", path.c_str()),
112 entry("INTERFACE=%s", interface.c_str()));
113 throw std::runtime_error("Error no matching service");
114 }
115 }
116 catch (const SdBusError& e)
117 {
118 log<level::ERR>("Error in mapper call", entry("ERROR=%s", e.what()),
119 entry("PATH=%s", path.c_str()),
120 entry("INTERFACE=%s", interface.c_str()));
121 throw;
122 }
123
124 return mapperResponse.begin()->first;
125}
126
127void setProperty(sdbusplus::bus::bus& bus, const std::string& path,
128 const std::string& interface, const std::string& property,
129 const std::string& value)
130{
131 sdbusplus::message::variant<std::string> variantValue = value;
132 std::string service = getService(bus, path, interface);
133
134 auto method = bus.new_method_call(service.c_str(), path.c_str(),
135 PROPERTY_INTERFACE, "Set");
136
137 method.append(interface, property, variantValue);
138 bus.call_noreply(method);
139
140 return;
141}
142
143void ScheduledHostTransition::hostTransition()
144{
145 auto hostPath = std::string{HOST_OBJPATH} + '0';
146
147 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
148
149 setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION, reqTrans);
150
151 log<level::INFO>("Set requestedTransition",
152 entry("REQUESTED_TRANSITION=%s", reqTrans.c_str()));
153}
154
155void ScheduledHostTransition::callback()
156{
157 // Stop timer, since we need to do host transition once only
158 timer.setEnabled(false);
159 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800160 // Set scheduledTime to 0 to disable host transition and update scheduled
161 // values
162 scheduledTime(0);
Carol Wang6a5db3d2020-02-21 10:12:01 +0800163}
164
Carol Wangef7abe12020-02-25 16:19:34 +0800165void ScheduledHostTransition::initialize()
166{
167 // Subscribe time change event
168 // Choose the MAX time that is possible to avoid mis fires.
169 constexpr itimerspec maxTime = {
170 {0, 0}, // it_interval
171 {TIME_T_MAX, 0}, // it_value
172 };
173
174 // Create and operate on a timer that delivers timer expiration
175 // notifications via a file descriptor.
176 timeFd = timerfd_create(CLOCK_REALTIME, 0);
177 if (timeFd == -1)
178 {
179 auto eno = errno;
180 log<level::ERR>("Failed to create timerfd", entry("ERRNO=%d", eno),
181 entry("RC=%d", timeFd));
182 throw std::system_error(eno, std::system_category());
183 }
184
185 // Starts the timer referred to by the file descriptor fd.
186 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
187 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
188 // as cancelable if the real-time clock undergoes a discontinuous change.
189 // In this way, we can monitor whether BMC time is changed or not.
190 auto r = timerfd_settime(
191 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
192 if (r != 0)
193 {
194 auto eno = errno;
195 log<level::ERR>("Failed to set timerfd", entry("ERRNO=%d", eno),
196 entry("RC=%d", r));
197 throw std::system_error(eno, std::system_category());
198 }
199
200 sd_event_source* es;
201 // Add a new I/O event source to an event loop. onTimeChange will be called
202 // when the event source is triggered.
203 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
204 if (r < 0)
205 {
206 auto eno = errno;
207 log<level::ERR>("Failed to add event", entry("ERRNO=%d", eno),
208 entry("RC=%d", r));
209 throw std::system_error(eno, std::system_category());
210 }
211 timeChangeEventSource.reset(es);
212}
213
214ScheduledHostTransition::~ScheduledHostTransition()
215{
216 close(timeFd);
217}
218
219void ScheduledHostTransition::handleTimeUpdates()
220{
Carol Wang1dbbef42020-03-09 11:51:23 +0800221 // Stop the timer if it's running.
222 // Don't return directly when timer is stopped, because the timer is always
223 // disabled after the BMC is rebooted.
224 if (timer.isEnabled())
Carol Wangef7abe12020-02-25 16:19:34 +0800225 {
Carol Wang1dbbef42020-03-09 11:51:23 +0800226 timer.setEnabled(false);
Carol Wangef7abe12020-02-25 16:19:34 +0800227 }
Carol Wangef7abe12020-02-25 16:19:34 +0800228
229 // Get scheduled time
230 auto schedTime = HostTransition::scheduledTime();
231
232 if (schedTime == 0)
233 {
234 log<level::INFO>("handleTimeUpdates: The function Scheduled Host "
235 "Transition is disabled.");
236 return;
237 }
238
239 auto deltaTime = seconds(schedTime) - getTime();
240 if (deltaTime <= seconds(0))
241 {
Carol Wangef7abe12020-02-25 16:19:34 +0800242 hostTransition();
Carol Wang1dbbef42020-03-09 11:51:23 +0800243 // Set scheduledTime to 0 to disable host transition and update
244 // scheduled values
245 scheduledTime(0);
Carol Wangef7abe12020-02-25 16:19:34 +0800246 }
247 else
248 {
249 // Start a timer to do host transition at scheduled time
250 timer.restart(deltaTime);
251 }
252}
253
254int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
255 uint32_t /* revents */,
256 void* userdata)
257{
258 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
259
260 std::array<char, 64> time{};
261
262 // We are not interested in the data here.
263 // So read until there is no new data here in the FD
264 while (read(fd, time.data(), time.max_size()) > 0)
265 ;
266
267 log<level::INFO>("BMC system time is changed");
268 schedHostTran->handleTimeUpdates();
269
270 return 0;
271}
272
Carol Wang1dbbef42020-03-09 11:51:23 +0800273void ScheduledHostTransition::serializeScheduledValues()
274{
275 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
276 std::ofstream os(path.c_str(), std::ios::binary);
277 cereal::JSONOutputArchive oarchive(os);
278
279 oarchive(HostTransition::scheduledTime(),
280 HostTransition::scheduledTransition());
281}
282
283bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
284 Transition& trans)
285{
286 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
287
288 try
289 {
290 if (fs::exists(path))
291 {
292 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
293 cereal::JSONInputArchive iarchive(is);
294 iarchive(time, trans);
295 return true;
296 }
297 }
298 catch (std::exception& e)
299 {
300 log<level::ERR>(e.what());
301 fs::remove(path);
302 }
303
304 return false;
305}
306
307void ScheduledHostTransition::restoreScheduledValues()
308{
309 uint64_t time;
310 Transition trans;
311 if (!deserializeScheduledValues(time, trans))
312 {
313 // set to default value
314 HostTransition::scheduledTime(0);
315 HostTransition::scheduledTransition(Transition::On);
316 }
317 else
318 {
319 HostTransition::scheduledTime(time);
320 HostTransition::scheduledTransition(trans);
321 // Rebooting BMC is something like the BMC time is changed,
322 // so go on with the same process as BMC time changed.
323 handleTimeUpdates();
324 }
325}
326
Carol Wang71230ef2020-02-18 17:39:49 +0800327} // namespace manager
328} // namespace state
329} // namespace phosphor