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