1 #include "config.h" 2 3 #include "chassis_state_manager.hpp" 4 5 #include "xyz/openbmc_project/Common/error.hpp" 6 #include "xyz/openbmc_project/State/Shutdown/Power/error.hpp" 7 8 #include <cereal/archives/json.hpp> 9 #include <phosphor-logging/elog-errors.hpp> 10 #include <phosphor-logging/log.hpp> 11 #include <sdbusplus/bus.hpp> 12 #include <sdbusplus/exception.hpp> 13 #include <sdeventplus/event.hpp> 14 #include <sdeventplus/exception.hpp> 15 16 #include <experimental/filesystem> 17 #include <fstream> 18 19 namespace phosphor 20 { 21 namespace state 22 { 23 namespace manager 24 { 25 26 // When you see server:: you know we're referencing our base class 27 namespace server = sdbusplus::xyz::openbmc_project::State::server; 28 29 using namespace phosphor::logging; 30 using sdbusplus::exception::SdBusError; 31 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 32 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout; 33 constexpr auto CHASSIS_STATE_POWEROFF_TGT = "obmc-chassis-poweroff@0.target"; 34 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT = 35 "obmc-chassis-hard-poweroff@0.target"; 36 constexpr auto CHASSIS_STATE_POWERON_TGT = "obmc-chassis-poweron@0.target"; 37 38 constexpr auto ACTIVE_STATE = "active"; 39 constexpr auto ACTIVATING_STATE = "activating"; 40 41 /* Map a transition to it's systemd target */ 42 const std::map<server::Chassis::Transition, std::string> SYSTEMD_TARGET_TABLE = 43 { 44 // Use the hard off target to ensure we shutdown immediately 45 {server::Chassis::Transition::Off, CHASSIS_STATE_HARD_POWEROFF_TGT}, 46 {server::Chassis::Transition::On, CHASSIS_STATE_POWERON_TGT}}; 47 48 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 49 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 50 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 51 52 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; 53 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; 54 55 void Chassis::subscribeToSystemdSignals() 56 { 57 try 58 { 59 auto method = this->bus.new_method_call( 60 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Subscribe"); 61 this->bus.call(method); 62 } 63 catch (const sdbusplus::exception::SdBusError& ex) 64 { 65 log<level::ERR>("Failed to subscribe to systemd signals", 66 entry("ERR=%s", ex.what())); 67 elog<InternalFailure>(); 68 } 69 70 return; 71 } 72 73 // TODO - Will be rewritten once sdbusplus client bindings are in place 74 // and persistent storage design is in place and sdbusplus 75 // has read property function 76 void Chassis::determineInitialState() 77 { 78 std::variant<int> pgood = -1; 79 auto method = this->bus.new_method_call( 80 "org.openbmc.control.Power", "/org/openbmc/control/power0", 81 "org.freedesktop.DBus.Properties", "Get"); 82 83 method.append("org.openbmc.control.Power", "pgood"); 84 try 85 { 86 auto reply = this->bus.call(method); 87 reply.read(pgood); 88 89 if (std::get<int>(pgood) == 1) 90 { 91 log<level::INFO>("Initial Chassis State will be On", 92 entry("CHASSIS_CURRENT_POWER_STATE=%s", 93 convertForMessage(PowerState::On).c_str())); 94 server::Chassis::currentPowerState(PowerState::On); 95 server::Chassis::requestedPowerTransition(Transition::On); 96 return; 97 } 98 else 99 { 100 // The system is off. If we think it should be on then 101 // we probably lost AC while up, so set a new state 102 // change time. 103 uint64_t lastTime; 104 PowerState lastState; 105 106 if (deserializeStateChangeTime(lastTime, lastState)) 107 { 108 if (lastState == PowerState::On) 109 { 110 report<Blackout>(); 111 setStateChangeTime(); 112 } 113 } 114 } 115 } 116 catch (const SdBusError& e) 117 { 118 // It's acceptable for the pgood state service to not be available 119 // since it will notify us of the pgood state when it comes up. 120 if (e.name() != nullptr && 121 strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0) 122 { 123 goto fail; 124 } 125 126 // Only log for unexpected error types. 127 log<level::ERR>("Error performing call to get pgood", 128 entry("ERROR=%s", e.what())); 129 goto fail; 130 } 131 132 fail: 133 log<level::INFO>("Initial Chassis State will be Off", 134 entry("CHASSIS_CURRENT_POWER_STATE=%s", 135 convertForMessage(PowerState::Off).c_str())); 136 server::Chassis::currentPowerState(PowerState::Off); 137 server::Chassis::requestedPowerTransition(Transition::Off); 138 139 return; 140 } 141 142 void Chassis::executeTransition(Transition tranReq) 143 { 144 auto sysdTarget = SYSTEMD_TARGET_TABLE.find(tranReq)->second; 145 146 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 147 SYSTEMD_INTERFACE, "StartUnit"); 148 149 method.append(sysdTarget); 150 method.append("replace"); 151 152 this->bus.call_noreply(method); 153 154 return; 155 } 156 157 bool Chassis::stateActive(const std::string& target) 158 { 159 std::variant<std::string> currentState; 160 sdbusplus::message::object_path unitTargetPath; 161 162 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 163 SYSTEMD_INTERFACE, "GetUnit"); 164 165 method.append(target); 166 167 try 168 { 169 auto result = this->bus.call(method); 170 result.read(unitTargetPath); 171 } 172 catch (const SdBusError& e) 173 { 174 log<level::ERR>("Error in GetUnit call", entry("ERROR=%s", e.what())); 175 return false; 176 } 177 178 method = this->bus.new_method_call( 179 SYSTEMD_SERVICE, 180 static_cast<const std::string&>(unitTargetPath).c_str(), 181 SYSTEMD_PROPERTY_IFACE, "Get"); 182 183 method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); 184 185 try 186 { 187 auto result = this->bus.call(method); 188 result.read(currentState); 189 } 190 catch (const SdBusError& e) 191 { 192 log<level::ERR>("Error in ActiveState Get", 193 entry("ERROR=%s", e.what())); 194 return false; 195 } 196 197 const auto& currentStateStr = std::get<std::string>(currentState); 198 return currentStateStr == ACTIVE_STATE || 199 currentStateStr == ACTIVATING_STATE; 200 } 201 202 int Chassis::sysStateChange(sdbusplus::message::message& msg) 203 { 204 sdbusplus::message::object_path newStateObjPath; 205 std::string newStateUnit{}; 206 std::string newStateResult{}; 207 208 // Read the msg and populate each variable 209 try 210 { 211 // newStateID is a throwaway that is needed in order to read the 212 // parameters that are useful out of the dbus message 213 uint32_t newStateID{}; 214 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 215 } 216 catch (const SdBusError& e) 217 { 218 log<level::ERR>("Error in state change - bad encoding", 219 entry("ERROR=%s", e.what()), 220 entry("REPLY_SIG=%s", msg.get_signature())); 221 return 0; 222 } 223 224 if ((newStateUnit == CHASSIS_STATE_POWEROFF_TGT) && 225 (newStateResult == "done") && (!stateActive(CHASSIS_STATE_POWERON_TGT))) 226 { 227 log<level::INFO>("Received signal that power OFF is complete"); 228 this->currentPowerState(server::Chassis::PowerState::Off); 229 this->setStateChangeTime(); 230 } 231 else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) && 232 (newStateResult == "done") && 233 (stateActive(CHASSIS_STATE_POWERON_TGT))) 234 { 235 log<level::INFO>("Received signal that power ON is complete"); 236 this->currentPowerState(server::Chassis::PowerState::On); 237 this->setStateChangeTime(); 238 } 239 240 return 0; 241 } 242 243 Chassis::Transition Chassis::requestedPowerTransition(Transition value) 244 { 245 246 log<level::INFO>("Change to Chassis Requested Power State", 247 entry("CHASSIS_REQUESTED_POWER_STATE=%s", 248 convertForMessage(value).c_str())); 249 executeTransition(value); 250 return server::Chassis::requestedPowerTransition(value); 251 } 252 253 Chassis::PowerState Chassis::currentPowerState(PowerState value) 254 { 255 PowerState chassisPowerState; 256 log<level::INFO>("Change to Chassis Power State", 257 entry("CHASSIS_CURRENT_POWER_STATE=%s", 258 convertForMessage(value).c_str())); 259 260 chassisPowerState = server::Chassis::currentPowerState(value); 261 pOHTimer.setEnabled(chassisPowerState == PowerState::On); 262 return chassisPowerState; 263 } 264 265 uint32_t Chassis::pOHCounter(uint32_t value) 266 { 267 if (value != pOHCounter()) 268 { 269 ChassisInherit::pOHCounter(value); 270 serializePOH(); 271 } 272 return pOHCounter(); 273 } 274 275 void Chassis::pOHCallback() 276 { 277 if (ChassisInherit::currentPowerState() == PowerState::On) 278 { 279 pOHCounter(pOHCounter() + 1); 280 } 281 } 282 283 void Chassis::restorePOHCounter() 284 { 285 uint32_t counter; 286 if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter)) 287 { 288 // set to default value 289 pOHCounter(0); 290 } 291 else 292 { 293 pOHCounter(counter); 294 } 295 } 296 297 fs::path Chassis::serializePOH(const fs::path& path) 298 { 299 std::ofstream os(path.c_str(), std::ios::binary); 300 cereal::JSONOutputArchive oarchive(os); 301 oarchive(pOHCounter()); 302 return path; 303 } 304 305 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pOHCounter) 306 { 307 try 308 { 309 if (fs::exists(path)) 310 { 311 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 312 cereal::JSONInputArchive iarchive(is); 313 iarchive(pOHCounter); 314 return true; 315 } 316 return false; 317 } 318 catch (cereal::Exception& e) 319 { 320 log<level::ERR>(e.what()); 321 fs::remove(path); 322 return false; 323 } 324 catch (const fs::filesystem_error& e) 325 { 326 return false; 327 } 328 329 return false; 330 } 331 332 void Chassis::startPOHCounter() 333 { 334 auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path(); 335 fs::create_directories(dir); 336 337 try 338 { 339 auto event = sdeventplus::Event::get_default(); 340 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 341 event.loop(); 342 } 343 catch (const sdeventplus::SdEventError& e) 344 { 345 log<level::ERR>("Error occurred during the sdeventplus loop", 346 entry("ERROR=%s", e.what())); 347 phosphor::logging::commit<InternalFailure>(); 348 } 349 } 350 351 void Chassis::serializeStateChangeTime() 352 { 353 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 354 std::ofstream os(path.c_str(), std::ios::binary); 355 cereal::JSONOutputArchive oarchive(os); 356 357 oarchive(ChassisInherit::lastStateChangeTime(), 358 ChassisInherit::currentPowerState()); 359 } 360 361 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state) 362 { 363 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 364 365 try 366 { 367 if (fs::exists(path)) 368 { 369 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 370 cereal::JSONInputArchive iarchive(is); 371 iarchive(time, state); 372 return true; 373 } 374 } 375 catch (std::exception& e) 376 { 377 log<level::ERR>(e.what()); 378 fs::remove(path); 379 } 380 381 return false; 382 } 383 384 void Chassis::restoreChassisStateChangeTime() 385 { 386 uint64_t time; 387 PowerState state; 388 389 if (!deserializeStateChangeTime(time, state)) 390 { 391 ChassisInherit::lastStateChangeTime(0); 392 } 393 else 394 { 395 ChassisInherit::lastStateChangeTime(time); 396 } 397 } 398 399 void Chassis::setStateChangeTime() 400 { 401 using namespace std::chrono; 402 uint64_t lastTime; 403 PowerState lastState; 404 405 auto now = 406 duration_cast<milliseconds>(system_clock::now().time_since_epoch()) 407 .count(); 408 409 // If power is on when the BMC is rebooted, this function will get called 410 // because sysStateChange() runs. Since the power state didn't change 411 // in this case, neither should the state change time, so check that 412 // the power state actually did change here. 413 if (deserializeStateChangeTime(lastTime, lastState)) 414 { 415 if (lastState == ChassisInherit::currentPowerState()) 416 { 417 return; 418 } 419 } 420 421 ChassisInherit::lastStateChangeTime(now); 422 serializeStateChangeTime(); 423 } 424 425 } // namespace manager 426 } // namespace state 427 } // namespace phosphor 428