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