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 #include <xyz/openbmc_project/State/Host/error.hpp>
15 
16 #include <chrono>
17 #include <filesystem>
18 #include <fstream>
19 
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 
26 namespace phosphor
27 {
28 namespace state
29 {
30 namespace manager
31 {
32 
33 PHOSPHOR_LOG2_USING;
34 
35 namespace fs = std::filesystem;
36 
37 using namespace std::chrono;
38 using namespace phosphor::logging;
39 using namespace xyz::openbmc_project::ScheduledTime;
40 using InvalidTimeError =
41     sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime;
42 using HostTransition =
43     sdbusplus::server::xyz::openbmc_project::state::ScheduledHostTransition;
44 using HostState = sdbusplus::server::xyz::openbmc_project::state::Host;
45 
46 constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition";
47 constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause";
48 
scheduledTime(uint64_t value)49 uint64_t ScheduledHostTransition::scheduledTime(uint64_t value)
50 {
51     info("A scheduled host transition request has been made for {TIME}", "TIME",
52          value);
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 
getTime()89 seconds ScheduledHostTransition::getTime()
90 {
91     auto now = system_clock::now();
92     return duration_cast<seconds>(now.time_since_epoch());
93 }
94 
hostTransition()95 void ScheduledHostTransition::hostTransition()
96 {
97     auto hostPath = std::string{HOST_OBJPATH} + std::to_string(id);
98 
99     auto reqTrans = convertForMessage(HostTransition::scheduledTransition());
100 
101     info("Trying to set requestedTransition to {REQUESTED_TRANSITION}",
102          "REQUESTED_TRANSITION", reqTrans);
103 
104     utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION,
105                        reqTrans);
106 
107     // Set RestartCause to indicate this transition is occurring due to a
108     // scheduled host transition as long as it's not an off request
109     if (HostTransition::scheduledTransition() != HostState::Transition::Off)
110     {
111         info("Set RestartCause to scheduled power on reason");
112         auto resCause =
113             convertForMessage(HostState::RestartCause::ScheduledPowerOn);
114         utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE,
115                            resCause);
116     }
117 }
118 
callback()119 void ScheduledHostTransition::callback()
120 {
121     // Stop timer, since we need to do host transition once only
122     timer.setEnabled(false);
123     hostTransition();
124     // Set scheduledTime to 0 to disable host transition and update scheduled
125     // values
126     scheduledTime(0);
127 }
128 
initialize()129 void ScheduledHostTransition::initialize()
130 {
131     // Subscribe time change event
132     // Choose the MAX time that is possible to avoid misfires.
133     constexpr itimerspec maxTime = {
134         {0, 0},                                     // it_interval
135         {system_clock::duration::max().count(), 0}, // it_value
136     };
137 
138     // Create and operate on a timer that delivers timer expiration
139     // notifications via a file descriptor.
140     timeFd = timerfd_create(CLOCK_REALTIME, 0);
141     if (timeFd == -1)
142     {
143         auto eno = errno;
144         error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO",
145               eno, "RC", timeFd);
146         throw std::system_error(eno, std::system_category());
147     }
148 
149     // Starts the timer referred to by the file descriptor fd.
150     // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME
151     // and the clock for this timer is CLOCK_REALTIME, then mark this timer
152     // as cancelable if the real-time clock undergoes a discontinuous change.
153     // In this way, we can monitor whether BMC time is changed or not.
154     auto r = timerfd_settime(
155         timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
156     if (r != 0)
157     {
158         auto eno = errno;
159         error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
160               "RC", r);
161         throw std::system_error(eno, std::system_category());
162     }
163 
164     sd_event_source* es;
165     // Add a new I/O event source to an event loop. onTimeChange will be called
166     // when the event source is triggered.
167     r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this);
168     if (r < 0)
169     {
170         auto eno = errno;
171         error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno,
172               "RC", r);
173         throw std::system_error(eno, std::system_category());
174     }
175     timeChangeEventSource.reset(es);
176 }
177 
~ScheduledHostTransition()178 ScheduledHostTransition::~ScheduledHostTransition()
179 {
180     close(timeFd);
181 }
182 
handleTimeUpdates()183 void ScheduledHostTransition::handleTimeUpdates()
184 {
185     // Stop the timer if it's running.
186     // Don't return directly when timer is stopped, because the timer is always
187     // disabled after the BMC is rebooted.
188     if (timer.isEnabled())
189     {
190         timer.setEnabled(false);
191     }
192 
193     // Get scheduled time
194     auto schedTime = HostTransition::scheduledTime();
195 
196     if (schedTime == 0)
197     {
198         debug(
199             "handleTimeUpdates: The function Scheduled Host Transition is disabled.");
200         return;
201     }
202 
203     auto deltaTime = seconds(schedTime) - getTime();
204     if (deltaTime <= seconds(0))
205     {
206         try
207         {
208             hostTransition();
209         }
210         catch (const sdbusplus::exception_t& e)
211         {
212             using BMCNotReady = sdbusplus::error::xyz::openbmc_project::state::
213                 host::BMCNotReady;
214             // If error indicates BMC is not at Ready error then reschedule for
215             // 60s later
216             if ((e.name() != nullptr) &&
217                 (e.name() == std::string_view(BMCNotReady::errName)))
218             {
219                 warning(
220                     "BMC is not at ready, reschedule transition request for 60s");
221                 timer.restart(seconds(60));
222                 return;
223             }
224             else
225             {
226                 throw;
227             }
228         }
229         // Set scheduledTime to 0 to disable host transition and update
230         // scheduled values
231         scheduledTime(0);
232     }
233     else
234     {
235         // Start a timer to do host transition at scheduled time
236         timer.restart(deltaTime);
237     }
238 }
239 
onTimeChange(sd_event_source *,int fd,uint32_t,void * userdata)240 int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd,
241                                           uint32_t /* revents */,
242                                           void* userdata)
243 {
244     auto schedHostTran = static_cast<ScheduledHostTransition*>(userdata);
245 
246     std::array<char, 64> time{};
247 
248     // We are not interested in the data here.
249     // So read until there is no new data here in the FD
250     while (read(fd, time.data(), time.max_size()) > 0)
251         ;
252 
253     debug("BMC system time is changed");
254     schedHostTran->handleTimeUpdates();
255 
256     return 0;
257 }
258 
serializeScheduledValues()259 void ScheduledHostTransition::serializeScheduledValues()
260 {
261     fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
262     std::ofstream os(path.c_str(), std::ios::binary);
263     cereal::JSONOutputArchive oarchive(os);
264 
265     oarchive(HostTransition::scheduledTime(),
266              HostTransition::scheduledTransition());
267 }
268 
deserializeScheduledValues(uint64_t & time,Transition & trans)269 bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time,
270                                                          Transition& trans)
271 {
272     fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH};
273 
274     try
275     {
276         if (fs::exists(path))
277         {
278             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
279             cereal::JSONInputArchive iarchive(is);
280             iarchive(time, trans);
281             return true;
282         }
283     }
284     catch (const std::exception& e)
285     {
286         error("deserialize exception: {ERROR}", "ERROR", e);
287         fs::remove(path);
288     }
289 
290     return false;
291 }
292 
restoreScheduledValues()293 void ScheduledHostTransition::restoreScheduledValues()
294 {
295     uint64_t time;
296     Transition trans;
297     if (!deserializeScheduledValues(time, trans))
298     {
299         // set to default value
300         HostTransition::scheduledTime(0);
301         HostTransition::scheduledTransition(Transition::On);
302     }
303     else
304     {
305         HostTransition::scheduledTime(time);
306         HostTransition::scheduledTransition(trans);
307         // Rebooting BMC is something like the BMC time is changed,
308         // so go on with the same process as BMC time changed.
309         handleTimeUpdates();
310     }
311 }
312 
313 } // namespace manager
314 } // namespace state
315 } // namespace phosphor
316