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