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