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