1 #include "config.h" 2 3 #include "chassis_state_manager.hpp" 4 5 #include "utils.hpp" 6 #include "xyz/openbmc_project/Common/error.hpp" 7 #include "xyz/openbmc_project/State/Shutdown/Power/error.hpp" 8 9 #include <cereal/archives/json.hpp> 10 #include <phosphor-logging/elog-errors.hpp> 11 #include <phosphor-logging/lg2.hpp> 12 #include <sdbusplus/bus.hpp> 13 #include <sdbusplus/exception.hpp> 14 #include <sdeventplus/event.hpp> 15 #include <sdeventplus/exception.hpp> 16 17 #include <filesystem> 18 #include <fstream> 19 20 namespace phosphor 21 { 22 namespace state 23 { 24 namespace manager 25 { 26 27 PHOSPHOR_LOG2_USING; 28 29 // When you see server:: you know we're referencing our base class 30 namespace server = sdbusplus::xyz::openbmc_project::State::server; 31 32 using namespace phosphor::logging; 33 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 34 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout; 35 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator; 36 constexpr auto CHASSIS_STATE_POWEROFF_TGT = "obmc-chassis-poweroff@0.target"; 37 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT = 38 "obmc-chassis-hard-poweroff@0.target"; 39 constexpr auto CHASSIS_STATE_POWERON_TGT = "obmc-chassis-poweron@0.target"; 40 41 constexpr auto ACTIVE_STATE = "active"; 42 constexpr auto ACTIVATING_STATE = "activating"; 43 44 // Details at https://upower.freedesktop.org/docs/Device.html 45 constexpr uint TYPE_UPS = 3; 46 constexpr uint STATE_FULLY_CHARGED = 4; 47 constexpr uint BATTERY_LVL_FULL = 8; 48 49 /* Map a transition to it's systemd target */ 50 const std::map<server::Chassis::Transition, std::string> SYSTEMD_TARGET_TABLE = 51 { 52 // Use the hard off target to ensure we shutdown immediately 53 {server::Chassis::Transition::Off, CHASSIS_STATE_HARD_POWEROFF_TGT}, 54 {server::Chassis::Transition::On, CHASSIS_STATE_POWERON_TGT}}; 55 56 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 57 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 58 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 59 60 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; 61 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; 62 63 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper"; 64 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper"; 65 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper"; 66 constexpr auto UPOWER_INTERFACE = "org.freedesktop.UPower.Device"; 67 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"; 68 69 void Chassis::subscribeToSystemdSignals() 70 { 71 try 72 { 73 auto method = this->bus.new_method_call( 74 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Subscribe"); 75 this->bus.call(method); 76 } 77 catch (const sdbusplus::exception::exception& e) 78 { 79 error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e); 80 elog<InternalFailure>(); 81 } 82 83 return; 84 } 85 86 // TODO - Will be rewritten once sdbusplus client bindings are in place 87 // and persistent storage design is in place and sdbusplus 88 // has read property function 89 void Chassis::determineInitialState() 90 { 91 92 // Monitor for any properties changed signals on UPower device path 93 uPowerPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>( 94 bus, 95 sdbusplus::bus::match::rules::propertiesChangedNamespace( 96 "/org/freedesktop/UPower", UPOWER_INTERFACE), 97 [this](auto& msg) { this->uPowerChangeEvent(msg); }); 98 99 determineStatusOfPower(); 100 101 std::variant<int> pgood = -1; 102 auto method = this->bus.new_method_call( 103 "org.openbmc.control.Power", "/org/openbmc/control/power0", 104 "org.freedesktop.DBus.Properties", "Get"); 105 106 method.append("org.openbmc.control.Power", "pgood"); 107 try 108 { 109 auto reply = this->bus.call(method); 110 reply.read(pgood); 111 112 if (std::get<int>(pgood) == 1) 113 { 114 info("Initial Chassis State will be On"); 115 server::Chassis::currentPowerState(PowerState::On); 116 server::Chassis::requestedPowerTransition(Transition::On); 117 return; 118 } 119 else 120 { 121 // The system is off. If we think it should be on then 122 // we probably lost AC while up, so set a new state 123 // change time. 124 uint64_t lastTime; 125 PowerState lastState; 126 127 if (deserializeStateChangeTime(lastTime, lastState)) 128 { 129 // If power was on before the BMC reboot and the reboot reason 130 // was not a pinhole reset, log an error 131 if (lastState == PowerState::On) 132 { 133 info( 134 "Chassis power was on before the BMC reboot and it is off now"); 135 setStateChangeTime(); 136 137 if (phosphor::state::manager::utils::getGpioValue( 138 "reset-cause-pinhole") != 1) 139 { 140 if (standbyVoltageRegulatorFault()) 141 { 142 report<Regulator>(); 143 } 144 else 145 { 146 report<Blackout>(); 147 } 148 } 149 } 150 } 151 } 152 } 153 catch (const sdbusplus::exception::exception& e) 154 { 155 // It's acceptable for the pgood state service to not be available 156 // since it will notify us of the pgood state when it comes up. 157 if (e.name() != nullptr && 158 strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0) 159 { 160 goto fail; 161 } 162 163 // Only log for unexpected error types. 164 error("Error performing call to get pgood: {ERROR}", "ERROR", e); 165 goto fail; 166 } 167 168 fail: 169 info("Initial Chassis State will be Off"); 170 server::Chassis::currentPowerState(PowerState::Off); 171 server::Chassis::requestedPowerTransition(Transition::Off); 172 173 return; 174 } 175 176 void Chassis::determineStatusOfPower() 177 { 178 // Default PowerStatus to good 179 server::Chassis::currentPowerStatus(PowerStatus::Good); 180 181 // Find all implementations of the UPower interface 182 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 183 MAPPER_INTERFACE, "GetSubTree"); 184 185 mapper.append("/", 0, std::vector<std::string>({UPOWER_INTERFACE})); 186 187 std::map<std::string, std::map<std::string, std::vector<std::string>>> 188 mapperResponse; 189 190 try 191 { 192 auto mapperResponseMsg = bus.call(mapper); 193 mapperResponseMsg.read(mapperResponse); 194 } 195 catch (const sdbusplus::exception::exception& e) 196 { 197 error("Error in mapper GetSubTree call for UPS: {ERROR}", "ERROR", e); 198 throw; 199 } 200 201 if (mapperResponse.empty()) 202 { 203 debug("No UPower devices found in system"); 204 } 205 206 // Iterate through all returned Upower interfaces and look for UPS's 207 for (const auto& [path, services] : mapperResponse) 208 { 209 for (const auto& serviceIter : services) 210 { 211 const std::string& service = serviceIter.first; 212 213 try 214 { 215 auto method = bus.new_method_call(service.c_str(), path.c_str(), 216 PROPERTY_INTERFACE, "GetAll"); 217 method.append(UPOWER_INTERFACE); 218 219 auto response = bus.call(method); 220 using Property = std::string; 221 using Value = std::variant<bool, uint>; 222 using PropertyMap = std::map<Property, Value>; 223 PropertyMap properties; 224 response.read(properties); 225 226 if (std::get<uint>(properties["Type"]) != TYPE_UPS) 227 { 228 info("UPower device {OBJ_PATH} is not a UPS device", 229 "OBJ_PATH", path); 230 continue; 231 } 232 233 if (std::get<bool>(properties["IsPresent"]) != true) 234 { 235 // There is a UPS detected but it is not officially 236 // "present" yet. Monitor it for state change. 237 info("UPower device {OBJ_PATH} is not present", "OBJ_PATH", 238 path); 239 continue; 240 } 241 242 if (std::get<uint>(properties["State"]) == STATE_FULLY_CHARGED) 243 { 244 info("UPS is fully charged"); 245 } 246 else 247 { 248 info("UPS is not fully charged: {UPS_STATE}", "UPS_STATE", 249 std::get<uint>(properties["State"])); 250 server::Chassis::currentPowerStatus( 251 PowerStatus::UninterruptiblePowerSupply); 252 return; 253 } 254 255 if (std::get<uint>(properties["BatteryLevel"]) == 256 BATTERY_LVL_FULL) 257 { 258 info("UPS Battery Level is Full"); 259 // Only one UPS per system, we've found it and it's all 260 // good so exit function 261 return; 262 } 263 else 264 { 265 info("UPS Battery Level is Low: {UPS_BAT_LEVEL}", 266 "UPS_BAT_LEVEL", 267 std::get<uint>(properties["BatteryLevel"])); 268 server::Chassis::currentPowerStatus( 269 PowerStatus::UninterruptiblePowerSupply); 270 return; 271 } 272 } 273 catch (const sdbusplus::exception::exception& e) 274 { 275 error("Error reading UPS property, error: {ERROR}, " 276 "service: {SERVICE} path: {PATH}", 277 "ERROR", e, "SERVICE", service, "PATH", path); 278 throw; 279 } 280 } 281 } 282 return; 283 } 284 285 void Chassis::uPowerChangeEvent(sdbusplus::message::message& msg) 286 { 287 debug("UPS Property Change Event Triggered"); 288 std::string statusInterface; 289 std::map<std::string, std::variant<uint, bool>> msgData; 290 msg.read(statusInterface, msgData); 291 292 // If the change is to any of the three properties we are interested in 293 // then call determineStatusOfPower() to see if a power status change 294 // is needed 295 auto propertyMap = msgData.find("IsPresent"); 296 if (propertyMap != msgData.end()) 297 { 298 info("UPS presence changed to {UPS_PRES_INFO}", "UPS_PRES_INFO", 299 std::get<bool>(propertyMap->second)); 300 determineStatusOfPower(); 301 return; 302 } 303 304 propertyMap = msgData.find("State"); 305 if (propertyMap != msgData.end()) 306 { 307 info("UPS State changed to {UPS_STATE}", "UPS_STATE", 308 std::get<uint>(propertyMap->second)); 309 determineStatusOfPower(); 310 return; 311 } 312 313 propertyMap = msgData.find("BatteryLevel"); 314 if (propertyMap != msgData.end()) 315 { 316 info("UPS BatteryLevel changed to {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL", 317 std::get<uint>(propertyMap->second)); 318 determineStatusOfPower(); 319 return; 320 } 321 return; 322 } 323 324 void Chassis::executeTransition(Transition tranReq) 325 { 326 auto sysdTarget = SYSTEMD_TARGET_TABLE.find(tranReq)->second; 327 328 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 329 SYSTEMD_INTERFACE, "StartUnit"); 330 331 method.append(sysdTarget); 332 method.append("replace"); 333 334 this->bus.call_noreply(method); 335 336 return; 337 } 338 339 bool Chassis::stateActive(const std::string& target) 340 { 341 std::variant<std::string> currentState; 342 sdbusplus::message::object_path unitTargetPath; 343 344 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 345 SYSTEMD_INTERFACE, "GetUnit"); 346 347 method.append(target); 348 349 try 350 { 351 auto result = this->bus.call(method); 352 result.read(unitTargetPath); 353 } 354 catch (const sdbusplus::exception::exception& e) 355 { 356 error("Error in GetUnit call: {ERROR}", "ERROR", e); 357 return false; 358 } 359 360 method = this->bus.new_method_call( 361 SYSTEMD_SERVICE, 362 static_cast<const std::string&>(unitTargetPath).c_str(), 363 SYSTEMD_PROPERTY_IFACE, "Get"); 364 365 method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); 366 367 try 368 { 369 auto result = this->bus.call(method); 370 result.read(currentState); 371 } 372 catch (const sdbusplus::exception::exception& e) 373 { 374 error("Error in ActiveState Get: {ERROR}", "ERROR", e); 375 return false; 376 } 377 378 const auto& currentStateStr = std::get<std::string>(currentState); 379 return currentStateStr == ACTIVE_STATE || 380 currentStateStr == ACTIVATING_STATE; 381 } 382 383 int Chassis::sysStateChange(sdbusplus::message::message& msg) 384 { 385 sdbusplus::message::object_path newStateObjPath; 386 std::string newStateUnit{}; 387 std::string newStateResult{}; 388 389 // Read the msg and populate each variable 390 try 391 { 392 // newStateID is a throwaway that is needed in order to read the 393 // parameters that are useful out of the dbus message 394 uint32_t newStateID{}; 395 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 396 } 397 catch (const sdbusplus::exception::exception& e) 398 { 399 error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}", 400 "ERROR", e, "REPLY_SIG", msg.get_signature()); 401 return 0; 402 } 403 404 if ((newStateUnit == CHASSIS_STATE_POWEROFF_TGT) && 405 (newStateResult == "done") && (!stateActive(CHASSIS_STATE_POWERON_TGT))) 406 { 407 info("Received signal that power OFF is complete"); 408 this->currentPowerState(server::Chassis::PowerState::Off); 409 this->setStateChangeTime(); 410 } 411 else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) && 412 (newStateResult == "done") && 413 (stateActive(CHASSIS_STATE_POWERON_TGT))) 414 { 415 info("Received signal that power ON is complete"); 416 this->currentPowerState(server::Chassis::PowerState::On); 417 this->setStateChangeTime(); 418 419 // Remove temporary file which is utilized for scenarios where the 420 // BMC is rebooted while the chassis power is still on. 421 // This file is used to indicate to chassis related systemd services 422 // that the chassis is already on and they should skip running. 423 // Once the chassis state is back to on we can clear this file. 424 auto size = std::snprintf(nullptr, 0, CHASSIS_ON_FILE, 0); 425 size++; // null 426 std::unique_ptr<char[]> chassisFile(new char[size]); 427 std::snprintf(chassisFile.get(), size, CHASSIS_ON_FILE, 0); 428 if (std::filesystem::exists(chassisFile.get())) 429 { 430 std::filesystem::remove(chassisFile.get()); 431 } 432 } 433 434 return 0; 435 } 436 437 Chassis::Transition Chassis::requestedPowerTransition(Transition value) 438 { 439 440 info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}", 441 "REQ_POWER_TRAN", value); 442 executeTransition(value); 443 return server::Chassis::requestedPowerTransition(value); 444 } 445 446 Chassis::PowerState Chassis::currentPowerState(PowerState value) 447 { 448 PowerState chassisPowerState; 449 info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE", 450 value); 451 452 chassisPowerState = server::Chassis::currentPowerState(value); 453 pohTimer.setEnabled(chassisPowerState == PowerState::On); 454 return chassisPowerState; 455 } 456 457 uint32_t Chassis::pohCounter(uint32_t value) 458 { 459 if (value != pohCounter()) 460 { 461 ChassisInherit::pohCounter(value); 462 serializePOH(); 463 } 464 return pohCounter(); 465 } 466 467 void Chassis::pohCallback() 468 { 469 if (ChassisInherit::currentPowerState() == PowerState::On) 470 { 471 pohCounter(pohCounter() + 1); 472 } 473 } 474 475 void Chassis::restorePOHCounter() 476 { 477 uint32_t counter; 478 if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter)) 479 { 480 // set to default value 481 pohCounter(0); 482 } 483 else 484 { 485 pohCounter(counter); 486 } 487 } 488 489 fs::path Chassis::serializePOH(const fs::path& path) 490 { 491 std::ofstream os(path.c_str(), std::ios::binary); 492 cereal::JSONOutputArchive oarchive(os); 493 oarchive(pohCounter()); 494 return path; 495 } 496 497 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pohCounter) 498 { 499 try 500 { 501 if (fs::exists(path)) 502 { 503 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 504 cereal::JSONInputArchive iarchive(is); 505 iarchive(pohCounter); 506 return true; 507 } 508 return false; 509 } 510 catch (const cereal::Exception& e) 511 { 512 error("deserialize exception: {ERROR}", "ERROR", e); 513 fs::remove(path); 514 return false; 515 } 516 catch (const fs::filesystem_error& e) 517 { 518 return false; 519 } 520 521 return false; 522 } 523 524 void Chassis::startPOHCounter() 525 { 526 auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path(); 527 fs::create_directories(dir); 528 529 try 530 { 531 auto event = sdeventplus::Event::get_default(); 532 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 533 event.loop(); 534 } 535 catch (const sdeventplus::SdEventError& e) 536 { 537 error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR", 538 e); 539 phosphor::logging::commit<InternalFailure>(); 540 } 541 } 542 543 void Chassis::serializeStateChangeTime() 544 { 545 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 546 std::ofstream os(path.c_str(), std::ios::binary); 547 cereal::JSONOutputArchive oarchive(os); 548 549 oarchive(ChassisInherit::lastStateChangeTime(), 550 ChassisInherit::currentPowerState()); 551 } 552 553 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state) 554 { 555 fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH}; 556 557 try 558 { 559 if (fs::exists(path)) 560 { 561 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 562 cereal::JSONInputArchive iarchive(is); 563 iarchive(time, state); 564 return true; 565 } 566 } 567 catch (const std::exception& e) 568 { 569 error("deserialize exception: {ERROR}", "ERROR", e); 570 fs::remove(path); 571 } 572 573 return false; 574 } 575 576 void Chassis::restoreChassisStateChangeTime() 577 { 578 uint64_t time; 579 PowerState state; 580 581 if (!deserializeStateChangeTime(time, state)) 582 { 583 ChassisInherit::lastStateChangeTime(0); 584 } 585 else 586 { 587 ChassisInherit::lastStateChangeTime(time); 588 } 589 } 590 591 void Chassis::setStateChangeTime() 592 { 593 using namespace std::chrono; 594 uint64_t lastTime; 595 PowerState lastState; 596 597 auto now = 598 duration_cast<milliseconds>(system_clock::now().time_since_epoch()) 599 .count(); 600 601 // If power is on when the BMC is rebooted, this function will get called 602 // because sysStateChange() runs. Since the power state didn't change 603 // in this case, neither should the state change time, so check that 604 // the power state actually did change here. 605 if (deserializeStateChangeTime(lastTime, lastState)) 606 { 607 if (lastState == ChassisInherit::currentPowerState()) 608 { 609 return; 610 } 611 } 612 613 ChassisInherit::lastStateChangeTime(now); 614 serializeStateChangeTime(); 615 } 616 617 bool Chassis::standbyVoltageRegulatorFault() 618 { 619 bool regulatorFault = false; 620 621 // find standby voltage regulator fault via gpiog 622 623 auto gpioval = utils::getGpioValue("regulator-standby-faulted"); 624 625 if (-1 == gpioval) 626 { 627 error("Failed reading regulator-standby-faulted GPIO"); 628 } 629 630 if (1 == gpioval) 631 { 632 info("Detected standby voltage regulator fault"); 633 regulatorFault = true; 634 } 635 636 return regulatorFault; 637 } 638 639 } // namespace manager 640 } // namespace state 641 } // namespace phosphor 642