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