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