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 #include <xyz/openbmc_project/State/Host/error.hpp> 15 16 #include <chrono> 17 #include <filesystem> 18 #include <fstream> 19 20 // Need to do this since its not exported outside of the kernel. 21 // Refer : https://gist.github.com/lethean/446cea944b7441228298 22 #ifndef TFD_TIMER_CANCEL_ON_SET 23 #define TFD_TIMER_CANCEL_ON_SET (1 << 1) 24 #endif 25 26 namespace phosphor 27 { 28 namespace state 29 { 30 namespace manager 31 { 32 33 PHOSPHOR_LOG2_USING; 34 35 namespace fs = std::filesystem; 36 37 using namespace std::chrono; 38 using namespace phosphor::logging; 39 using namespace xyz::openbmc_project::ScheduledTime; 40 using InvalidTimeError = 41 sdbusplus::xyz::openbmc_project::ScheduledTime::Error::InvalidTime; 42 using HostTransition = 43 sdbusplus::server::xyz::openbmc_project::state::ScheduledHostTransition; 44 using HostState = sdbusplus::server::xyz::openbmc_project::state::Host; 45 46 constexpr auto PROPERTY_TRANSITION = "RequestedHostTransition"; 47 constexpr auto PROPERTY_RESTART_CAUSE = "RestartCause"; 48 49 uint64_t ScheduledHostTransition::scheduledTime(uint64_t value) 50 { 51 info("A scheduled host transition request has been made for {TIME}", "TIME", 52 value); 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} + std::to_string(id); 98 99 auto reqTrans = convertForMessage(HostTransition::scheduledTransition()); 100 101 info("Trying to set requestedTransition to {REQUESTED_TRANSITION}", 102 "REQUESTED_TRANSITION", reqTrans); 103 104 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_TRANSITION, 105 reqTrans); 106 107 // Set RestartCause to indicate this transition is occurring due to a 108 // scheduled host transition as long as it's not an off request 109 if (HostTransition::scheduledTransition() != HostState::Transition::Off) 110 { 111 info("Set RestartCause to scheduled power on reason"); 112 auto resCause = 113 convertForMessage(HostState::RestartCause::ScheduledPowerOn); 114 utils::setProperty(bus, hostPath, HOST_BUSNAME, PROPERTY_RESTART_CAUSE, 115 resCause); 116 } 117 } 118 119 void ScheduledHostTransition::callback() 120 { 121 // Stop timer, since we need to do host transition once only 122 timer.setEnabled(false); 123 hostTransition(); 124 // Set scheduledTime to 0 to disable host transition and update scheduled 125 // values 126 scheduledTime(0); 127 } 128 129 void ScheduledHostTransition::initialize() 130 { 131 // Subscribe time change event 132 // Choose the MAX time that is possible to avoid misfires. 133 constexpr itimerspec maxTime = { 134 {0, 0}, // it_interval 135 {system_clock::duration::max().count(), 0}, // it_value 136 }; 137 138 // Create and operate on a timer that delivers timer expiration 139 // notifications via a file descriptor. 140 timeFd = timerfd_create(CLOCK_REALTIME, 0); 141 if (timeFd == -1) 142 { 143 auto eno = errno; 144 error("Failed to create timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", 145 eno, "RC", timeFd); 146 throw std::system_error(eno, std::system_category()); 147 } 148 149 // Starts the timer referred to by the file descriptor fd. 150 // If TFD_TIMER_CANCEL_ON_SET is specified along with TFD_TIMER_ABSTIME 151 // and the clock for this timer is CLOCK_REALTIME, then mark this timer 152 // as cancelable if the real-time clock undergoes a discontinuous change. 153 // In this way, we can monitor whether BMC time is changed or not. 154 auto r = timerfd_settime( 155 timeFd, TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &maxTime, nullptr); 156 if (r != 0) 157 { 158 auto eno = errno; 159 error("Failed to set timerfd, errno: {ERRNO}, rc: {RC}", "ERRNO", eno, 160 "RC", r); 161 throw std::system_error(eno, std::system_category()); 162 } 163 164 sd_event_source* es; 165 // Add a new I/O event source to an event loop. onTimeChange will be called 166 // when the event source is triggered. 167 r = sd_event_add_io(event.get(), &es, timeFd, EPOLLIN, onTimeChange, this); 168 if (r < 0) 169 { 170 auto eno = errno; 171 error("Failed to add event, errno: {ERRNO}, rc: {RC}", "ERRNO", eno, 172 "RC", r); 173 throw std::system_error(eno, std::system_category()); 174 } 175 timeChangeEventSource.reset(es); 176 } 177 178 ScheduledHostTransition::~ScheduledHostTransition() 179 { 180 close(timeFd); 181 } 182 183 void ScheduledHostTransition::handleTimeUpdates() 184 { 185 // Stop the timer if it's running. 186 // Don't return directly when timer is stopped, because the timer is always 187 // disabled after the BMC is rebooted. 188 if (timer.isEnabled()) 189 { 190 timer.setEnabled(false); 191 } 192 193 // Get scheduled time 194 auto schedTime = HostTransition::scheduledTime(); 195 196 if (schedTime == 0) 197 { 198 debug( 199 "handleTimeUpdates: The function Scheduled Host Transition is disabled."); 200 return; 201 } 202 203 auto deltaTime = seconds(schedTime) - getTime(); 204 if (deltaTime <= seconds(0)) 205 { 206 try 207 { 208 hostTransition(); 209 } 210 catch (const sdbusplus::exception_t& e) 211 { 212 using BMCNotReady = sdbusplus::error::xyz::openbmc_project::state:: 213 host::BMCNotReady; 214 // If error indicates BMC is not at Ready error then reschedule for 215 // 60s later 216 if ((e.name() != nullptr) && 217 (e.name() == std::string_view(BMCNotReady::errName))) 218 { 219 warning( 220 "BMC is not at ready, reschedule transition request for 60s"); 221 timer.restart(seconds(60)); 222 return; 223 } 224 else 225 { 226 throw; 227 } 228 } 229 // Set scheduledTime to 0 to disable host transition and update 230 // scheduled values 231 scheduledTime(0); 232 } 233 else 234 { 235 // Start a timer to do host transition at scheduled time 236 timer.restart(deltaTime); 237 } 238 } 239 240 int ScheduledHostTransition::onTimeChange(sd_event_source* /* es */, int fd, 241 uint32_t /* revents */, 242 void* userdata) 243 { 244 auto* schedHostTran = static_cast<ScheduledHostTransition*>(userdata); 245 246 std::array<char, 64> time{}; 247 248 // We are not interested in the data here. 249 // So read until there is no new data here in the FD 250 while (read(fd, time.data(), time.max_size()) > 0) 251 ; 252 253 debug("BMC system time is changed"); 254 schedHostTran->handleTimeUpdates(); 255 256 return 0; 257 } 258 259 void ScheduledHostTransition::serializeScheduledValues() 260 { 261 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH}; 262 std::ofstream os(path.c_str(), std::ios::binary); 263 cereal::JSONOutputArchive oarchive(os); 264 265 oarchive(HostTransition::scheduledTime(), 266 HostTransition::scheduledTransition()); 267 } 268 269 bool ScheduledHostTransition::deserializeScheduledValues(uint64_t& time, 270 Transition& trans) 271 { 272 fs::path path{SCHEDULED_HOST_TRANSITION_PERSIST_PATH}; 273 274 try 275 { 276 if (fs::exists(path)) 277 { 278 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 279 cereal::JSONInputArchive iarchive(is); 280 iarchive(time, trans); 281 return true; 282 } 283 } 284 catch (const std::exception& e) 285 { 286 error("deserialize exception: {ERROR}", "ERROR", e); 287 fs::remove(path); 288 } 289 290 return false; 291 } 292 293 void ScheduledHostTransition::restoreScheduledValues() 294 { 295 uint64_t time; 296 Transition trans; 297 if (!deserializeScheduledValues(time, trans)) 298 { 299 // set to default value 300 HostTransition::scheduledTime(0); 301 HostTransition::scheduledTransition(Transition::On); 302 } 303 else 304 { 305 HostTransition::scheduledTime(time); 306 HostTransition::scheduledTransition(trans); 307 // Rebooting BMC is something like the BMC time is changed, 308 // so go on with the same process as BMC time changed. 309 handleTimeUpdates(); 310 } 311 } 312 313 } // namespace manager 314 } // namespace state 315 } // namespace phosphor 316