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