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 <gpiod.h> 9 10 #include <cereal/archives/json.hpp> 11 #include <phosphor-logging/elog-errors.hpp> 12 #include <phosphor-logging/lg2.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 PHOSPHOR_LOG2_USING; 29 30 // When you see server:: you know we're referencing our base class 31 namespace server = sdbusplus::xyz::openbmc_project::State::server; 32 33 using namespace phosphor::logging; 34 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 35 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout; 36 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator; 37 constexpr auto CHASSIS_STATE_POWEROFF_TGT = "obmc-chassis-poweroff@0.target"; 38 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT = 39 "obmc-chassis-hard-poweroff@0.target"; 40 constexpr auto CHASSIS_STATE_POWERON_TGT = "obmc-chassis-poweron@0.target"; 41 42 constexpr auto ACTIVE_STATE = "active"; 43 constexpr auto ACTIVATING_STATE = "activating"; 44 45 /* Map a transition to it's systemd target */ 46 const std::map<server::Chassis::Transition, std::string> SYSTEMD_TARGET_TABLE = 47 { 48 // Use the hard off target to ensure we shutdown immediately 49 {server::Chassis::Transition::Off, CHASSIS_STATE_HARD_POWEROFF_TGT}, 50 {server::Chassis::Transition::On, CHASSIS_STATE_POWERON_TGT}}; 51 52 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 53 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 54 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 55 56 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; 57 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; 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::exception& e) 68 { 69 error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e); 70 elog<InternalFailure>(); 71 } 72 73 return; 74 } 75 76 // TODO - Will be rewritten once sdbusplus client bindings are in place 77 // and persistent storage design is in place and sdbusplus 78 // has read property function 79 void Chassis::determineInitialState() 80 { 81 std::variant<int> pgood = -1; 82 auto method = this->bus.new_method_call( 83 "org.openbmc.control.Power", "/org/openbmc/control/power0", 84 "org.freedesktop.DBus.Properties", "Get"); 85 86 method.append("org.openbmc.control.Power", "pgood"); 87 try 88 { 89 auto reply = this->bus.call(method); 90 reply.read(pgood); 91 92 if (std::get<int>(pgood) == 1) 93 { 94 info("Initial Chassis State will be On"); 95 server::Chassis::currentPowerState(PowerState::On); 96 server::Chassis::requestedPowerTransition(Transition::On); 97 return; 98 } 99 else 100 { 101 // The system is off. If we think it should be on then 102 // we probably lost AC while up, so set a new state 103 // change time. 104 uint64_t lastTime; 105 PowerState lastState; 106 107 if (deserializeStateChangeTime(lastTime, lastState)) 108 { 109 if (lastState == PowerState::On) 110 { 111 if (standbyVoltageRegulatorFault()) 112 { 113 report<Regulator>(); 114 } 115 else 116 { 117 report<Blackout>(); 118 } 119 120 setStateChangeTime(); 121 } 122 } 123 } 124 } 125 catch (const sdbusplus::exception::exception& e) 126 { 127 // It's acceptable for the pgood state service to not be available 128 // since it will notify us of the pgood state when it comes up. 129 if (e.name() != nullptr && 130 strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0) 131 { 132 goto fail; 133 } 134 135 // Only log for unexpected error types. 136 error("Error performing call to get pgood: {ERROR}", "ERROR", e); 137 goto fail; 138 } 139 140 fail: 141 info("Initial Chassis State will be Off"); 142 server::Chassis::currentPowerState(PowerState::Off); 143 server::Chassis::requestedPowerTransition(Transition::Off); 144 145 return; 146 } 147 148 void Chassis::executeTransition(Transition tranReq) 149 { 150 auto sysdTarget = SYSTEMD_TARGET_TABLE.find(tranReq)->second; 151 152 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 153 SYSTEMD_INTERFACE, "StartUnit"); 154 155 method.append(sysdTarget); 156 method.append("replace"); 157 158 this->bus.call_noreply(method); 159 160 return; 161 } 162 163 bool Chassis::stateActive(const std::string& target) 164 { 165 std::variant<std::string> currentState; 166 sdbusplus::message::object_path unitTargetPath; 167 168 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 169 SYSTEMD_INTERFACE, "GetUnit"); 170 171 method.append(target); 172 173 try 174 { 175 auto result = this->bus.call(method); 176 result.read(unitTargetPath); 177 } 178 catch (const sdbusplus::exception::exception& e) 179 { 180 error("Error in GetUnit call: {ERROR}", "ERROR", e); 181 return false; 182 } 183 184 method = this->bus.new_method_call( 185 SYSTEMD_SERVICE, 186 static_cast<const std::string&>(unitTargetPath).c_str(), 187 SYSTEMD_PROPERTY_IFACE, "Get"); 188 189 method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); 190 191 try 192 { 193 auto result = this->bus.call(method); 194 result.read(currentState); 195 } 196 catch (const sdbusplus::exception::exception& e) 197 { 198 error("Error in ActiveState Get: {ERROR}", "ERROR", e); 199 return false; 200 } 201 202 const auto& currentStateStr = std::get<std::string>(currentState); 203 return currentStateStr == ACTIVE_STATE || 204 currentStateStr == ACTIVATING_STATE; 205 } 206 207 int Chassis::sysStateChange(sdbusplus::message::message& msg) 208 { 209 sdbusplus::message::object_path newStateObjPath; 210 std::string newStateUnit{}; 211 std::string newStateResult{}; 212 213 // Read the msg and populate each variable 214 try 215 { 216 // newStateID is a throwaway that is needed in order to read the 217 // parameters that are useful out of the dbus message 218 uint32_t newStateID{}; 219 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 220 } 221 catch (const sdbusplus::exception::exception& e) 222 { 223 error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}", 224 "ERROR", e, "REPLY_SIG", 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 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 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 info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}", 265 "REQ_POWER_TRAN", value); 266 executeTransition(value); 267 return server::Chassis::requestedPowerTransition(value); 268 } 269 270 Chassis::PowerState Chassis::currentPowerState(PowerState value) 271 { 272 PowerState chassisPowerState; 273 info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE", 274 value); 275 276 chassisPowerState = server::Chassis::currentPowerState(value); 277 pohTimer.setEnabled(chassisPowerState == PowerState::On); 278 return chassisPowerState; 279 } 280 281 uint32_t Chassis::pohCounter(uint32_t value) 282 { 283 if (value != pohCounter()) 284 { 285 ChassisInherit::pohCounter(value); 286 serializePOH(); 287 } 288 return pohCounter(); 289 } 290 291 void Chassis::pohCallback() 292 { 293 if (ChassisInherit::currentPowerState() == PowerState::On) 294 { 295 pohCounter(pohCounter() + 1); 296 } 297 } 298 299 void Chassis::restorePOHCounter() 300 { 301 uint32_t counter; 302 if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter)) 303 { 304 // set to default value 305 pohCounter(0); 306 } 307 else 308 { 309 pohCounter(counter); 310 } 311 } 312 313 fs::path Chassis::serializePOH(const fs::path& path) 314 { 315 std::ofstream os(path.c_str(), std::ios::binary); 316 cereal::JSONOutputArchive oarchive(os); 317 oarchive(pohCounter()); 318 return path; 319 } 320 321 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pohCounter) 322 { 323 try 324 { 325 if (fs::exists(path)) 326 { 327 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 328 cereal::JSONInputArchive iarchive(is); 329 iarchive(pohCounter); 330 return true; 331 } 332 return false; 333 } 334 catch (const cereal::Exception& e) 335 { 336 error("deserialize exception: {ERROR}", "ERROR", e); 337 fs::remove(path); 338 return false; 339 } 340 catch (const fs::filesystem_error& e) 341 { 342 return false; 343 } 344 345 return false; 346 } 347 348 void Chassis::startPOHCounter() 349 { 350 auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path(); 351 fs::create_directories(dir); 352 353 try 354 { 355 auto event = sdeventplus::Event::get_default(); 356 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 357 event.loop(); 358 } 359 catch (const sdeventplus::SdEventError& e) 360 { 361 error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR", 362 e); 363 phosphor::logging::commit<InternalFailure>(); 364 } 365 } 366 367 void Chassis::serializeStateChangeTime() 368 { 369 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 370 std::ofstream os(path.c_str(), std::ios::binary); 371 cereal::JSONOutputArchive oarchive(os); 372 373 oarchive(ChassisInherit::lastStateChangeTime(), 374 ChassisInherit::currentPowerState()); 375 } 376 377 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state) 378 { 379 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 380 381 try 382 { 383 if (fs::exists(path)) 384 { 385 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 386 cereal::JSONInputArchive iarchive(is); 387 iarchive(time, state); 388 return true; 389 } 390 } 391 catch (const std::exception& e) 392 { 393 error("deserialize exception: {ERROR}", "ERROR", e); 394 fs::remove(path); 395 } 396 397 return false; 398 } 399 400 void Chassis::restoreChassisStateChangeTime() 401 { 402 uint64_t time; 403 PowerState state; 404 405 if (!deserializeStateChangeTime(time, state)) 406 { 407 ChassisInherit::lastStateChangeTime(0); 408 } 409 else 410 { 411 ChassisInherit::lastStateChangeTime(time); 412 } 413 } 414 415 void Chassis::setStateChangeTime() 416 { 417 using namespace std::chrono; 418 uint64_t lastTime; 419 PowerState lastState; 420 421 auto now = 422 duration_cast<milliseconds>(system_clock::now().time_since_epoch()) 423 .count(); 424 425 // If power is on when the BMC is rebooted, this function will get called 426 // because sysStateChange() runs. Since the power state didn't change 427 // in this case, neither should the state change time, so check that 428 // the power state actually did change here. 429 if (deserializeStateChangeTime(lastTime, lastState)) 430 { 431 if (lastState == ChassisInherit::currentPowerState()) 432 { 433 return; 434 } 435 } 436 437 ChassisInherit::lastStateChangeTime(now); 438 serializeStateChangeTime(); 439 } 440 441 bool Chassis::standbyVoltageRegulatorFault() 442 { 443 bool regulatorFault = false; 444 445 // find standby voltage regulator fault via gpio 446 gpiod_line* line = gpiod_line_find("regulator-standby-faulted"); 447 448 if (nullptr != line) 449 { 450 // take ownership of gpio 451 if (0 != gpiod_line_request_input(line, "chassis")) 452 { 453 error("Failed request for regulator-standby-faulted GPIO"); 454 } 455 else 456 { 457 // get gpio value 458 auto gpioval = gpiod_line_get_value(line); 459 460 // release ownership of gpio 461 gpiod_line_close_chip(line); 462 463 if (-1 == gpioval) 464 { 465 error("Failed reading regulator-standby-faulted GPIO"); 466 } 467 468 if (1 == gpioval) 469 { 470 info("Detected standby voltage regulator fault"); 471 regulatorFault = true; 472 } 473 } 474 } 475 return regulatorFault; 476 } 477 478 } // namespace manager 479 } // namespace state 480 } // namespace phosphor 481