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