blob: 9fdd32b137a7aa5e4bef6c973f7139e1864f0fdb [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>
7#include <chrono>
Carol Wangef7abe12020-02-25 16:19:34 +08008#include <sys/timerfd.h>
9#include <unistd.h>
10
11// Need to do this since its not exported outside of the kernel.
12// Refer : https://gist.github.com/lethean/446cea944b7441228298
13#ifndef TFD_TIMER_CANCEL_ON_SET
14#define TFD_TIMER_CANCEL_ON_SET (1 << 1)
15#endif
16
17// Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
18#define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
Carol Wang4ca6f3f2020-02-19 16:28:59 +080019
Carol Wang71230ef2020-02-18 17:39:49 +080020namespace phosphor
21{
22namespace state
23{
24namespace manager
25{
26
Carol Wang4ca6f3f2020-02-19 16:28:59 +080027using namespace std::chrono;
28using namespace phosphor::logging;
29using namespace xyz::openbmc_project::ScheduledTime;
Carol Wang6a5db3d2020-02-21 10:12:01 +080030using sdbusplus::exception::SdBusError;
Carol Wang4ca6f3f2020-02-19 16:28:59 +080031using InvalidTimeError =
32 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
Carol Wang71230ef2020-02-18 17:39:49 +080033using HostTransition =
34 sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
35
Carol Wang6a5db3d2020-02-21 10:12:01 +080036constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
37constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
38constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
39constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
40constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
41
Carol Wang71230ef2020-02-18 17:39:49 +080042uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
43{
Carol Wang4ca6f3f2020-02-19 16:28:59 +080044 if (value == 0)
45 {
46 // 0 means the function Scheduled Host Transition is disabled
Carol Wang6a5db3d2020-02-21 10:12:01 +080047 // Stop the timer if it's running
48 if (timer.isEnabled())
49 {
50 timer.setEnabled(false);
51 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080052
Carol Wangef7abe12020-02-25 16:19:34 +080053 log<level::INFO>("scheduledTime: The function Scheduled Host "
54 "Transition is disabled.");
Carol Wang4ca6f3f2020-02-19 16:28:59 +080055 }
56 else
57 {
Carol Wang6a5db3d2020-02-21 10:12:01 +080058 auto deltaTime = seconds(value) - getTime();
59 if (deltaTime < seconds(0))
60 {
61 log<level::ERR>(
62 "Scheduled time is earlier than current time. Fail to "
63 "schedule host transition.");
64 elog<InvalidTimeError>(
65 InvalidTime::REASON("Scheduled time is in the past"));
66 }
67 else
68 {
69 // Start a timer to do host transition at scheduled time
70 timer.restart(deltaTime);
71 }
Carol Wang4ca6f3f2020-02-19 16:28:59 +080072 }
Carol Wang6a5db3d2020-02-21 10:12:01 +080073
74 // Set and return the scheduled time
Carol Wang71230ef2020-02-18 17:39:49 +080075 return HostTransition::scheduledTime(value);
76}
77
Carol Wang4ca6f3f2020-02-19 16:28:59 +080078seconds ScheduledHostTransition::getTime()
79{
80 auto now = system_clock::now();
81 return duration_cast<seconds>(now.time_since_epoch());
82}
83
Carol Wang6a5db3d2020-02-21 10:12:01 +080084std::string getService(sdbusplus::bus::bus& bus, std::string path,
85 std::string interface)
86{
87 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
88 MAPPER_INTERFACE, "GetObject");
89
90 mapper.append(path, std::vector<std::string>({interface}));
91
92 std::vector<std::pair<std::string, std::vector<std::string>>>
93 mapperResponse;
94
95 try
96 {
97 auto mapperResponseMsg = bus.call(mapper);
98
99 mapperResponseMsg.read(mapperResponse);
100 if (mapperResponse.empty())
101 {
102 log<level::ERR>("Error no matching service",
103 entry("PATH=%s", path.c_str()),
104 entry("INTERFACE=%s", interface.c_str()));
105 throw std::runtime_error("Error no matching service");
106 }
107 }
108 catch (const SdBusError& e)
109 {
110 log<level::ERR>("Error in mapper call", entry("ERROR=%s", e.what()),
111 entry("PATH=%s", path.c_str()),
112 entry("INTERFACE=%s", interface.c_str()));
113 throw;
114 }
115
116 return mapperResponse.begin()->first;
117}
118
119void setProperty(sdbusplus::bus::bus& bus, const std::string& path,
120 const std::string& interface, const std::string& property,
121 const std::string& value)
122{
123 sdbusplus::message::variant<std::string> variantValue = value;
124 std::string service = getService(bus, path, interface);
125
126 auto method = bus.new_method_call(service.c_str(), path.c_str(),
127 PROPERTY_INTERFACE, "Set");
128
129 method.append(interface, property, variantValue);
130 bus.call_noreply(method);
131
132 return;
133}
134
135void ScheduledHostTransition::hostTransition()
136{
137 auto hostPath = std::string{HOST_OBJPATH} + '0';
138
139 auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
140
141 setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION, reqTrans);
142
143 log<level::INFO>("Set requestedTransition",
144 entry("REQUESTED_TRANSITION=%s", reqTrans.c_str()));
145}
146
147void ScheduledHostTransition::callback()
148{
149 // Stop timer, since we need to do host transition once only
150 timer.setEnabled(false);
151 hostTransition();
152 // Set scheduledTime to 0 to disable host transition
153 HostTransition::scheduledTime(0);
154}
155
Carol Wangef7abe12020-02-25 16:19:34 +0800156void ScheduledHostTransition::initialize()
157{
158 // Subscribe time change event
159 // Choose the MAX time that is possible to avoid mis fires.
160 constexpr itimerspec maxTime = {
161 {0, 0}, // it_interval
162 {TIME_T_MAX, 0}, // it_value
163 };
164
165 // Create and operate on a timer that delivers timer expiration
166 // notifications via a file descriptor.
167 timeFd = timerfd_create(CLOCK_REALTIME, 0);
168 if (timeFd == -1)
169 {
170 auto eno = errno;
171 log<level::ERR>("Failed to create timerfd", entry("ERRNO=%d", eno),
172 entry("RC=%d", timeFd));
173 throw std::system_error(eno, std::system_category());
174 }
175
176 // Starts the timer referred to by the file descriptor fd.
177 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
178 // and the clock for this timer is CLOCK_REALTIME, then mark this timer
179 // as cancelable if the real-time clock undergoes a discontinuous change.
180 // In this way, we can monitor whether BMC time is changed or not.
181 auto r = timerfd_settime(
182 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
183 if (r != 0)
184 {
185 auto eno = errno;
186 log<level::ERR>("Failed to set timerfd", entry("ERRNO=%d", eno),
187 entry("RC=%d", r));
188 throw std::system_error(eno, std::system_category());
189 }
190
191 sd_event_source* es;
192 // Add a new I/O event source to an event loop. onTimeChange will be called
193 // when the event source is triggered.
194 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
195 if (r < 0)
196 {
197 auto eno = errno;
198 log<level::ERR>("Failed to add event", entry("ERRNO=%d", eno),
199 entry("RC=%d", r));
200 throw std::system_error(eno, std::system_category());
201 }
202 timeChangeEventSource.reset(es);
203}
204
205ScheduledHostTransition::~ScheduledHostTransition()
206{
207 close(timeFd);
208}
209
210void ScheduledHostTransition::handleTimeUpdates()
211{
212 if (!timer.isEnabled())
213 {
214 return;
215 }
216 // Stop the timer if it's running
217 timer.setEnabled(false);
218
219 // Get scheduled time
220 auto schedTime = HostTransition::scheduledTime();
221
222 if (schedTime == 0)
223 {
224 log<level::INFO>("handleTimeUpdates: The function Scheduled Host "
225 "Transition is disabled.");
226 return;
227 }
228
229 auto deltaTime = seconds(schedTime) - getTime();
230 if (deltaTime <= seconds(0))
231 {
232 // When BMC time is changed to be later than scheduled time, check the
233 // state of host transition to decide whether need to do host transition
234 hostTransition();
235 // Set scheduledTime to 0 to disable host transition
236 HostTransition::scheduledTime(0);
237 }
238 else
239 {
240 // Start a timer to do host transition at scheduled time
241 timer.restart(deltaTime);
242 }
243}
244
245int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
246 uint32_t /* revents */,
247 void* userdata)
248{
249 auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
250
251 std::array<char, 64> time{};
252
253 // We are not interested in the data here.
254 // So read until there is no new data here in the FD
255 while (read(fd, time.data(), time.max_size()) > 0)
256 ;
257
258 log<level::INFO>("BMC system time is changed");
259 schedHostTran->handleTimeUpdates();
260
261 return 0;
262}
263
Carol Wang71230ef2020-02-18 17:39:49 +0800264} // namespace manager
265} // namespace state
266} // namespace phosphor