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