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