1 #include "scheduled_host_transition.hpp"
2 
3 #include "utils.hpp"
4 #include "xyz/openbmc_project/State/Host/server.hpp"
5 
6 #include <sys/timerfd.h>
7 #include <unistd.h>
8 
9 #include <cereal/archives/json.hpp>
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/elog.hpp>
12 #include <phosphor-logging/lg2.hpp>
13 #include <xyz/openbmc_project/ScheduledTime/error.hpp>
14 
15 #include <chrono>
16 #include <filesystem>
17 #include <fstream>
18 
19 // Need to do this since its not exported outside of the kernel.
20 // Refer : https://gist.github.com/lethean/446cea944b7441228298
21 #ifndef TFD_TIMER_CANCEL_ON_SET
22 #define TFD_TIMER_CANCEL_ON_SET (1 << 1)
23 #endif
24 
25 // Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
26 #define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
27 
28 namespace phosphor
29 {
30 namespace state
31 {
32 namespace manager
33 {
34 
35 PHOSPHOR_LOG2_USING;
36 
37 namespace fs = std::filesystem;
38 
39 using namespace std::chrono;
40 using namespace phosphor::logging;
41 using namespace xyz::openbmc_project::ScheduledTime;
42 using InvalidTimeError =
43     sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
44 using HostTransition =
45     sdbusplus::xyz::openbmc_project::State::server::ScheduledHostTransition;
46 using HostState = sdbusplus::xyz::openbmc_project::State::server::Host;
47 
48 constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
49 constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause";
50 
51 uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
52 {
53     if (value == 0)
54     {
55         // 0 means the function Scheduled Host Transition is disabled
56         // Stop the timer if it's running
57         if (timer.isEnabled())
58         {
59             timer.setEnabled(false);
60             debug(
61                 "scheduledTime: The function Scheduled Host Transition is disabled.");
62         }
63     }
64     else
65     {
66         auto deltaTime = seconds(value) - getTime();
67         if (deltaTime < seconds(0))
68         {
69             error(
70                 "Scheduled time is earlier than current time. Fail to schedule host transition.");
71             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         }
79     }
80 
81     // Set scheduledTime
82     HostTransition::scheduledTime(value);
83     // Store scheduled values
84     serializeScheduledValues();
85 
86     return value;
87 }
88 
89 seconds ScheduledHostTransition::getTime()
90 {
91     auto now = system_clock::now();
92     return duration_cast<seconds>(now.time_since_epoch());
93 }
94 
95 void ScheduledHostTransition::hostTransition()
96 {
97     auto hostPath = std::string{HOST_OBJPATH} + '0';
98 
99     // Set RestartCause to indicate this transition is occurring due to a
100     // scheduled host transition as long as it's not an off request
101     if (HostTransition::scheduledTransition() != HostState::Transition::Off)
102     {
103         info("Set RestartCause to scheduled power on reason");
104         auto resCause =
105             convertForMessage(HostState::RestartCause::ScheduledPowerOn);
106         utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE,
107                            resCause);
108     }
109     auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
110 
111     utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
112                        reqTrans);
113 
114     info("Set requestedTransition to {REQUESTED_TRANSITION}",
115          "REQUESTED_TRANSITION", reqTrans);
116 }
117 
118 void ScheduledHostTransition::callback()
119 {
120     // Stop timer, since we need to do host transition once only
121     timer.setEnabled(false);
122     hostTransition();
123     // Set scheduledTime to 0 to disable host transition and update scheduled
124     // values
125     scheduledTime(0);
126 }
127 
128 void ScheduledHostTransition::initialize()
129 {
130     // Subscribe time change event
131     // Choose the MAX time that is possible to avoid mis fires.
132     constexpr itimerspec maxTime = {
133         {0, 0},          // it_interval
134         {TIME_T_MAX, 0}, // it_value
135     };
136 
137     // Create and operate on a timer that delivers timer expiration
138     // notifications via a file descriptor.
139     timeFd = timerfd_create(CLOCK_REALTIME, 0);
140     if (timeFd == -1)
141     {
142         auto eno = errno;
143         error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
144               eno, "RC", timeFd);
145         throw std::system_error(eno, std::system_category());
146     }
147 
148     // Starts the timer referred to by the file descriptor fd.
149     // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
150     // and the clock for this timer is CLOCK_REALTIME, then mark this timer
151     // as cancelable if the real-time clock undergoes a discontinuous change.
152     // In this way, we can monitor whether BMC time is changed or not.
153     auto r = timerfd_settime(
154         timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
155     if (r != 0)
156     {
157         auto eno = errno;
158         error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
159               "RC", r);
160         throw std::system_error(eno, std::system_category());
161     }
162 
163     sd_event_source* es;
164     // Add a new I/O event source to an event loop. onTimeChange will be called
165     // when the event source is triggered.
166     r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
167     if (r < 0)
168     {
169         auto eno = errno;
170         error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
171               "RC", r);
172         throw std::system_error(eno, std::system_category());
173     }
174     timeChangeEventSource.reset(es);
175 }
176 
177 ScheduledHostTransition::~ScheduledHostTransition()
178 {
179     close(timeFd);
180 }
181 
182 void ScheduledHostTransition::handleTimeUpdates()
183 {
184     // Stop the timer if it's running.
185     // Don't return directly when timer is stopped, because the timer is always
186     // disabled after the BMC is rebooted.
187     if (timer.isEnabled())
188     {
189         timer.setEnabled(false);
190     }
191 
192     // Get scheduled time
193     auto schedTime = HostTransition::scheduledTime();
194 
195     if (schedTime == 0)
196     {
197         debug(
198             "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
199         return;
200     }
201 
202     auto deltaTime = seconds(schedTime) - getTime();
203     if (deltaTime <= seconds(0))
204     {
205         hostTransition();
206         // Set scheduledTime to 0 to disable host transition and update
207         // scheduled values
208         scheduledTime(0);
209     }
210     else
211     {
212         // Start a timer to do host transition at scheduled time
213         timer.restart(deltaTime);
214     }
215 }
216 
217 int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
218                                           uint32_t /* revents */,
219                                           void* userdata)
220 {
221     auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
222 
223     std::array<char, 64> time{};
224 
225     // We are not interested in the data here.
226     // So read until there is no new data here in the FD
227     while (read(fd, time.data(), time.max_size()) > 0)
228         ;
229 
230     debug("BMC system time is changed");
231     schedHostTran->handleTimeUpdates();
232 
233     return 0;
234 }
235 
236 void ScheduledHostTransition::serializeScheduledValues()
237 {
238     fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
239     std::ofstream os(path.c_str(), std::ios::binary);
240     cereal::JSONOutputArchive oarchive(os);
241 
242     oarchive(HostTransition::scheduledTime(),
243              HostTransition::scheduledTransition());
244 }
245 
246 bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
247                                                          Transition& trans)
248 {
249     fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
250 
251     try
252     {
253         if (fs::exists(path))
254         {
255             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
256             cereal::JSONInputArchive iarchive(is);
257             iarchive(time, trans);
258             return true;
259         }
260     }
261     catch (const std::exception& e)
262     {
263         error("deserialize exception: {ERROR}", "ERROR", e);
264         fs::remove(path);
265     }
266 
267     return false;
268 }
269 
270 void ScheduledHostTransition::restoreScheduledValues()
271 {
272     uint64_t time;
273     Transition trans;
274     if (!deserializeScheduledValues(time, trans))
275     {
276         // set to default value
277         HostTransition::scheduledTime(0);
278         HostTransition::scheduledTransition(Transition::On);
279     }
280     else
281     {
282         HostTransition::scheduledTime(time);
283         HostTransition::scheduledTransition(trans);
284         // Rebooting BMC is something like the BMC time is changed,
285         // so go on with the same process as BMC time changed.
286         handleTimeUpdates();
287     }
288 }
289 
290 } // namespace manager
291 } // namespace state
292 } // namespace phosphor
293