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