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/log.hpp>
11 #include <xyz/openbmc_project/Common/error.hpp>
12 
13 // Need to do this since its not exported outside of the kernel.
14 // Refer : https://gist.github.com/lethean/446cea944b7441228298
15 #ifndef TFD_TIMER_CANCEL_ON_SET
16 #define TFD_TIMER_CANCEL_ON_SET (1 << 1)
17 #endif
18 
19 // Needed to make sure timerfd does not misfire even though we set CANCEL_ON_SET
20 #define TIME_T_MAX (time_t)((1UL << ((sizeof(time_t) << 3) - 1)) - 1)
21 
22 namespace phosphor
23 {
24 namespace time
25 {
26 namespace server = sdbusplus::xyz::openbmc_project::Time::server;
27 using namespace phosphor::logging;
28 
29 BmcEpoch::BmcEpoch(sdbusplus::bus::bus& bus, const char* objPath) :
30     EpochBase(bus, objPath), bus(bus)
31 {
32     initialize();
33 }
34 
35 void BmcEpoch::initialize()
36 {
37     using InternalFailure =
38         sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
39 
40     // Subscribe time change event
41     // Choose the MAX time that is possible to avoid mis fires.
42     constexpr itimerspec maxTime = {
43         {0, 0},          // it_interval
44         {TIME_T_MAX, 0}, // it_value
45     };
46 
47     timeFd = timerfd_create(CLOCK_REALTIME, 0);
48     if (timeFd == -1)
49     {
50         log<level::ERR>("Failed to create timerfd", entry("ERRNO=%d", errno),
51                         entry("ERR=%s", strerror(errno)));
52         elog<InternalFailure>();
53     }
54 
55     auto r = timerfd_settime(
56         timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr);
57     if (r != 0)
58     {
59         log<level::ERR>("Failed to set timerfd", entry("ERRNO=%d", errno),
60                         entry("ERR=%s", strerror(errno)));
61         elog<InternalFailure>();
62     }
63 
64     sd_event_source* es;
65     r = sd_event_add_io(bus.get_event(), &es, timeFd, EPOLLIN, onTimeChange,
66                         this);
67     if (r < 0)
68     {
69         log<level::ERR>("Failed to add event", entry("ERRNO=%d", -r),
70                         entry("ERR=%s", strerror(-r)));
71         elog<InternalFailure>();
72     }
73     timeChangeEventSource.reset(es);
74 }
75 
76 BmcEpoch::~BmcEpoch()
77 {
78     close(timeFd);
79 }
80 
81 uint64_t BmcEpoch::elapsed() const
82 {
83     return getTime().count();
84 }
85 
86 uint64_t BmcEpoch::elapsed(uint64_t value)
87 {
88     /*
89         Mode  | Set BMC Time
90         ----- | -------------
91         NTP   | Fail to set
92         MANUAL| OK
93     */
94     auto time = microseconds(value);
95     setTime(time);
96 
97     server::EpochTime::elapsed(value);
98     return value;
99 }
100 
101 int BmcEpoch::onTimeChange(sd_event_source* /* es */, int fd,
102                            uint32_t /* revents */, void* /* userdata */)
103 {
104     std::array<char, 64> time{};
105 
106     // We are not interested in the data here.
107     // So read until there is no new data here in the FD
108     while (read(fd, time.data(), time.max_size()) > 0)
109         ;
110 
111     return 0;
112 }
113 
114 } // namespace time
115 } // namespace phosphor
116