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 sdbusplus::message::object_path newStateObjPath; 202 std::string newStateUnit{}; 203 std::string newStateResult{}; 204 205 // Read the msg and populate each variable 206 try 207 { 208 // newStateID is a throwaway that is needed in order to read the 209 // parameters that are useful out of the dbus message 210 uint32_t newStateID{}; 211 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 212 } 213 catch (const SdBusError& e) 214 { 215 log<level::ERR>("Error in state change - bad encoding", 216 entry("ERROR=%s", e.what()), 217 entry("REPLY_SIG=%s", msg.get_signature())); 218 return 0; 219 } 220 221 if ((newStateUnit == CHASSIS_STATE_POWEROFF_TGT) && 222 (newStateResult == "done") && (!stateActive(CHASSIS_STATE_POWERON_TGT))) 223 { 224 log<level::INFO>("Received signal that power OFF is complete"); 225 this->currentPowerState(server::Chassis::PowerState::Off); 226 this->setStateChangeTime(); 227 } 228 else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) && 229 (newStateResult == "done") && 230 (stateActive(CHASSIS_STATE_POWERON_TGT))) 231 { 232 log<level::INFO>("Received signal that power ON is complete"); 233 this->currentPowerState(server::Chassis::PowerState::On); 234 this->setStateChangeTime(); 235 } 236 237 return 0; 238 } 239 240 Chassis::Transition Chassis::requestedPowerTransition(Transition value) 241 { 242 243 log<level::INFO>("Change to Chassis Requested Power State", 244 entry("CHASSIS_REQUESTED_POWER_STATE=%s", 245 convertForMessage(value).c_str())); 246 executeTransition(value); 247 return server::Chassis::requestedPowerTransition(value); 248 } 249 250 Chassis::PowerState Chassis::currentPowerState(PowerState value) 251 { 252 PowerState chassisPowerState; 253 log<level::INFO>("Change to Chassis Power State", 254 entry("CHASSIS_CURRENT_POWER_STATE=%s", 255 convertForMessage(value).c_str())); 256 257 chassisPowerState = server::Chassis::currentPowerState(value); 258 pOHTimer.setEnabled(chassisPowerState == PowerState::On); 259 return chassisPowerState; 260 } 261 262 uint32_t Chassis::pOHCounter(uint32_t value) 263 { 264 if (value != pOHCounter()) 265 { 266 ChassisInherit::pOHCounter(value); 267 serializePOH(); 268 } 269 return pOHCounter(); 270 } 271 272 void Chassis::pOHCallback() 273 { 274 if (ChassisInherit::currentPowerState() == PowerState::On) 275 { 276 pOHCounter(pOHCounter() + 1); 277 } 278 } 279 280 void Chassis::restorePOHCounter() 281 { 282 uint32_t counter; 283 if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter)) 284 { 285 // set to default value 286 pOHCounter(0); 287 } 288 else 289 { 290 pOHCounter(counter); 291 } 292 } 293 294 fs::path Chassis::serializePOH(const fs::path& path) 295 { 296 std::ofstream os(path.c_str(), std::ios::binary); 297 cereal::JSONOutputArchive oarchive(os); 298 oarchive(pOHCounter()); 299 return path; 300 } 301 302 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pOHCounter) 303 { 304 try 305 { 306 if (fs::exists(path)) 307 { 308 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 309 cereal::JSONInputArchive iarchive(is); 310 iarchive(pOHCounter); 311 return true; 312 } 313 return false; 314 } 315 catch (cereal::Exception& e) 316 { 317 log<level::ERR>(e.what()); 318 fs::remove(path); 319 return false; 320 } 321 catch (const fs::filesystem_error& e) 322 { 323 return false; 324 } 325 326 return false; 327 } 328 329 void Chassis::startPOHCounter() 330 { 331 auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path(); 332 fs::create_directories(dir); 333 334 try 335 { 336 auto event = sdeventplus::Event::get_default(); 337 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 338 event.loop(); 339 } 340 catch (const sdeventplus::SdEventError& e) 341 { 342 log<level::ERR>("Error occurred during the sdeventplus loop", 343 entry("ERROR=%s", e.what())); 344 phosphor::logging::commit<InternalFailure>(); 345 } 346 } 347 348 void Chassis::serializeStateChangeTime() 349 { 350 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 351 std::ofstream os(path.c_str(), std::ios::binary); 352 cereal::JSONOutputArchive oarchive(os); 353 354 oarchive(ChassisInherit::lastStateChangeTime(), 355 ChassisInherit::currentPowerState()); 356 } 357 358 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state) 359 { 360 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 361 362 try 363 { 364 if (fs::exists(path)) 365 { 366 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 367 cereal::JSONInputArchive iarchive(is); 368 iarchive(time, state); 369 return true; 370 } 371 } 372 catch (std::exception& e) 373 { 374 log<level::ERR>(e.what()); 375 fs::remove(path); 376 } 377 378 return false; 379 } 380 381 void Chassis::restoreChassisStateChangeTime() 382 { 383 uint64_t time; 384 PowerState state; 385 386 if (!deserializeStateChangeTime(time, state)) 387 { 388 ChassisInherit::lastStateChangeTime(0); 389 } 390 else 391 { 392 ChassisInherit::lastStateChangeTime(time); 393 } 394 } 395 396 void Chassis::setStateChangeTime() 397 { 398 using namespace std::chrono; 399 uint64_t lastTime; 400 PowerState lastState; 401 402 auto now = 403 duration_cast<milliseconds>(system_clock::now().time_since_epoch()) 404 .count(); 405 406 // If power is on when the BMC is rebooted, this function will get called 407 // because sysStateChange() runs. Since the power state didn't change 408 // in this case, neither should the state change time, so check that 409 // the power state actually did change here. 410 if (deserializeStateChangeTime(lastTime, lastState)) 411 { 412 if (lastState == ChassisInherit::currentPowerState()) 413 { 414 return; 415 } 416 } 417 418 ChassisInherit::lastStateChangeTime(now); 419 serializeStateChangeTime(); 420 } 421 422 } // namespace manager 423 } // namespace state 424 } // namespace phosphor 425