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