1 #include "bmc_epoch.hpp"
2 
3 #include "utils.hpp"
4 
5 #include <sys/timerfd.h>
6 #include <unistd.h>
7 
8 #include <phosphor-logging/elog-errors.hpp>
9 #include <phosphor-logging/elog.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12 #include <xyz/openbmc_project/Time/error.hpp>
13 
14 // Need to do this since its not exported outside of the kernel.
15 // Refer : https://gist.github.com/lethean/446cea944b7441228298
16 #ifndef TFD_TIMER_CANCEL_ON_SET
17 #define TFD_TIMER_CANCEL_ON_SET (1 << 1)
18 #endif
19 
20 // Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
21 #define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
22 
23 namespace phosphor
24 {
25 namespace time
26 {
27 namespace // anonymous
28 {
29 constexpr auto SYSTEMD_TIME_SERVICE = "org.freedesktop.timedate1";
30 constexpr auto SYSTEMD_TIME_PATH = "/org/freedesktop/timedate1";
31 constexpr auto SYSTEMD_TIME_INTERFACE = "org.freedesktop.timedate1";
32 constexpr auto METHOD_SET_TIME = "SetTime";
33 } // namespace
34 
35 namespace server = sdbusplus::xyz::openbmc_project::Time::server;
36 using namespace phosphor::logging;
37 using FailedError = sdbusplus::xyz::openbmc_project::Time::Error::Failed;
38 
39 void BmcEpoch::initialize()
40 {
41     using InternalFailure =
42         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
43 
44     // Subscribe time change event
45     // Choose the MAX time that is possible to avoid mis fires.
46     constexpr itimerspec maxTime = {
47         {0, 0},          // it_interval
48         {TIME_T_MAX, 0}, // it_value
49     };
50 
51     timeFd = timerfd_create(CLOCK_REALTIME, 0);
52     if (timeFd == -1)
53     {
54         lg2::error("Failed to create timerfd: {ERRNO}", "ERRNO", errno);
55         elog<InternalFailure>();
56     }
57 
58     auto r = timerfd_settime(
59         timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
60     if (r != 0)
61     {
62         lg2::error("Failed to set timerfd: {ERRNO}", "ERRNO", errno);
63         elog<InternalFailure>();
64     }
65 
66     sd_event_source* es;
67     r = sd_event_add_io(bus.get_event(), &es, timeFd, EPOLLIN, onTimeChange,
68                         this);
69     if (r < 0)
70     {
71         lg2::error("Failed to add event: {ERRNO}", "ERRNO", errno);
72         elog<InternalFailure>();
73     }
74     timeChangeEventSource.reset(es);
75 }
76 
77 BmcEpoch::~BmcEpoch()
78 {
79     close(timeFd);
80 }
81 
82 uint64_t BmcEpoch::elapsed() const
83 {
84     return getTime().count();
85 }
86 
87 uint64_t BmcEpoch::elapsed(uint64_t value)
88 {
89     /*
90         Mode  | Set BMC Time
91         ----- | -------------
92         NTP   | Fail to set
93         MANUAL| OK
94     */
95     auto time = microseconds(value);
96     setTime(time);
97 
98     server::EpochTime::elapsed(value);
99     return value;
100 }
101 
102 int BmcEpoch::onTimeChange(sd_event_source* /* es */, int fd,
103                            uint32_t /* revents */, void* /* userdata */)
104 {
105     std::array<char, 64> time{};
106 
107     // We are not interested in the data here.
108     // So read until there is no new data here in the FD
109     while (read(fd, time.data(), time.max_size()) > 0)
110         ;
111 
112     return 0;
113 }
114 
115 void BmcEpoch::onModeChanged(Mode mode)
116 {
117     manager.setTimeMode(mode);
118 }
119 
120 bool BmcEpoch::setTime(const microseconds& usec)
121 {
122     auto method = bus.new_method_call(SYSTEMD_TIME_SERVICE, SYSTEMD_TIME_PATH,
123                                       SYSTEMD_TIME_INTERFACE, METHOD_SET_TIME);
124     method.append(static_cast<int64_t>(usec.count()),
125                   false,  // relative
126                   false); // user_interaction
127 
128     try
129     {
130         bus.call_noreply(method);
131     }
132     catch (const sdbusplus::exception_t& ex)
133     {
134         lg2::error("Error in setting system time: {ERROR}", "ERROR", ex);
135         using namespace xyz::openbmc_project::Time;
136         elog<FailedError>(Failed::REASON(ex.what()));
137     }
138     return true;
139 }
140 
141 microseconds BmcEpoch::getTime() const
142 {
143     auto now = system_clock::now();
144     return duration_cast<microseconds>(now.time_since_epoch());
145 }
146 
147 } // namespace time
148 } // namespace phosphor
149