#include "config.h" #include "chassis_state_manager.hpp" #include "utils.hpp" #include "xyz/openbmc_project/Common/error.hpp" #include "xyz/openbmc_project/State/Shutdown/Power/error.hpp" #include <cereal/archives/json.hpp> #include <org/freedesktop/UPower/Device/client.hpp> #include <phosphor-logging/elog-errors.hpp> #include <phosphor-logging/lg2.hpp> #include <sdbusplus/bus.hpp> #include <sdbusplus/exception.hpp> #include <sdeventplus/event.hpp> #include <sdeventplus/exception.hpp> #include <xyz/openbmc_project/ObjectMapper/client.hpp> #include <xyz/openbmc_project/State/Chassis/error.hpp> #include <xyz/openbmc_project/State/Decorator/PowerSystemInputs/server.hpp> #include <filesystem> #include <format> #include <fstream> namespace phosphor { namespace state { namespace manager { PHOSPHOR_LOG2_USING; // When you see server:: you know we're referencing our base class namespace server = sdbusplus::server::xyz::openbmc_project::state; namespace decoratorServer = sdbusplus::server::xyz::openbmc_project::state::decorator; using ObjectMapper = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>; using UPowerDevice = sdbusplus::client::org::freedesktop::u_power::Device<>; using namespace phosphor::logging; using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout; using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator; constexpr auto CHASSIS_STATE_POWEROFF_TGT_FMT = "obmc-chassis-poweroff@{}.target"; constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT_FMT = "obmc-chassis-hard-poweroff@{}.target"; constexpr auto CHASSIS_STATE_POWERON_TGT_FMT = "obmc-chassis-poweron@{}.target"; constexpr auto CHASSIS_BLACKOUT_TGT_FMT = "obmc-chassis-blackout@{}.target"; constexpr auto CHASSIS_STATE_POWERCYCLE_TGT_FMT = "obmc-chassis-powercycle@{}.target"; constexpr auto AUTO_POWER_RESTORE_SVC_FMT = "phosphor-discover-system-state@{}.service"; constexpr auto ACTIVE_STATE = "active"; constexpr auto ACTIVATING_STATE = "activating"; // Details at https://upower.freedesktop.org/docs/Device.html constexpr uint TYPE_UPS = 3; constexpr uint STATE_FULLY_CHARGED = 4; constexpr uint BATTERY_LVL_FULL = 8; constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"; void Chassis::createSystemdTargetTable() { systemdTargetTable = { // Use the hard off target to ensure we shutdown immediately {Transition::Off, std::format(CHASSIS_STATE_HARD_POWEROFF_TGT_FMT, id)}, {Transition::On, std::format(CHASSIS_STATE_POWERON_TGT_FMT, id)}, {Transition::PowerCycle, std::format(CHASSIS_STATE_POWERCYCLE_TGT_FMT, id)}}; } // TODO - Will be rewritten once sdbusplus client bindings are in place // and persistent storage design is in place and sdbusplus // has read property function void Chassis::determineInitialState() { // Monitor for any properties changed signals on UPower device path uPowerPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>( bus, sdbusplus::bus::match::rules::propertiesChangedNamespace( "/org/freedesktop/UPower", UPowerDevice::interface), [this](auto& msg) { this->uPowerChangeEvent(msg); }); // Monitor for any properties changed signals on PowerSystemInputs powerSysInputsPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>( bus, sdbusplus::bus::match::rules::propertiesChangedNamespace( std::format( "/xyz/openbmc_project/power/power_supplies/chassis{}/psus", id), decoratorServer::PowerSystemInputs::interface), [this](auto& msg) { this->powerSysInputsChangeEvent(msg); }); determineStatusOfPower(); std::variant<int> pgood = -1; auto method = this->bus.new_method_call( "org.openbmc.control.Power", "/org/openbmc/control/power0", "org.freedesktop.DBus.Properties", "Get"); method.append("org.openbmc.control.Power", "pgood"); try { auto reply = this->bus.call(method); reply.read(pgood); if (std::get<int>(pgood) == 1) { info("Initial Chassis State will be On"); server::Chassis::currentPowerState(PowerState::On); server::Chassis::requestedPowerTransition(Transition::On); return; } else { // The system is off. If we think it should be on then // we probably lost AC while up, so set a new state // change time. uint64_t lastTime; PowerState lastState; if (deserializeStateChangeTime(lastTime, lastState)) { // If power was on before the BMC reboot and the reboot reason // was not a pinhole reset, log an error if (lastState == PowerState::On) { info( "Chassis power was on before the BMC reboot and it is off now"); // Reset host sensors since system is off now // Ensure Power Leds are off. startUnit(std::format(CHASSIS_BLACKOUT_TGT_FMT, id)); setStateChangeTime(); // Generate file indicating AC loss occurred std::string chassisLostPowerFileFmt = std::format(CHASSIS_LOST_POWER_FILE, id); fs::create_directories(BASE_FILE_DIR); fs::path chassisPowerLossFile{chassisLostPowerFileFmt}; std::ofstream outfile(chassisPowerLossFile); outfile.close(); // 0 indicates pinhole reset. 1 is NOT pinhole reset if (phosphor::state::manager::utils::getGpioValue( "reset-cause-pinhole") != 0) { if (standbyVoltageRegulatorFault()) { report<Regulator>(); } else { report<Blackout>(Entry::Level::Critical); } } else { info("Pinhole reset"); } } } } } catch (const sdbusplus::exception_t& e) { // It's acceptable for the pgood state service to not be available // since it will notify us of the pgood state when it comes up. if (e.name() != nullptr && strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0) { goto fail; } // Only log for unexpected error types. error("Error performing call to get pgood: {ERROR}", "ERROR", e); goto fail; } fail: info("Initial Chassis State will be Off"); server::Chassis::currentPowerState(PowerState::Off); server::Chassis::requestedPowerTransition(Transition::Off); return; } void Chassis::determineStatusOfPower() { auto initialPowerStatus = server::Chassis::currentPowerStatus(); bool powerGood = determineStatusOfUPSPower(); if (!powerGood) { return; } powerGood = determineStatusOfPSUPower(); if (powerGood) { // All checks passed, set power status to good server::Chassis::currentPowerStatus(PowerStatus::Good); // If power status transitioned from bad to good and chassis power is // off then call Auto Power Restart to see if the system should auto // power on now that power status is good if ((initialPowerStatus != PowerStatus::Good) && (server::Chassis::currentPowerState() == PowerState::Off)) { info("power status transitioned from {START_PWR_STATE} to Good and " "chassis power is off, calling APR", "START_PWR_STATE", initialPowerStatus); restartUnit(std::format(AUTO_POWER_RESTORE_SVC_FMT, this->id)); } } } bool Chassis::determineStatusOfUPSPower() { // Find all implementations of the UPower interface auto mapper = bus.new_method_call(ObjectMapper::default_service, ObjectMapper::instance_path, ObjectMapper::interface, "GetSubTree"); mapper.append("/", 0, std::vector<std::string>({UPowerDevice::interface})); std::map<std::string, std::map<std::string, std::vector<std::string>>> mapperResponse; try { auto mapperResponseMsg = bus.call(mapper); mapperResponseMsg.read(mapperResponse); } catch (const sdbusplus::exception_t& e) { error("Error in mapper GetSubTree call for UPS: {ERROR}", "ERROR", e); throw; } if (mapperResponse.empty()) { debug("No UPower devices found in system"); } // Iterate through all returned Upower interfaces and look for UPS's for (const auto& [path, services] : mapperResponse) { for (const auto& serviceIter : services) { const std::string& service = serviceIter.first; try { auto method = bus.new_method_call(service.c_str(), path.c_str(), PROPERTY_INTERFACE, "GetAll"); method.append(UPowerDevice::interface); auto response = bus.call(method); using Property = std::string; using Value = std::variant<bool, uint>; using PropertyMap = std::map<Property, Value>; PropertyMap properties; response.read(properties); if (std::get<uint>(properties["Type"]) != TYPE_UPS) { info("UPower device {OBJ_PATH} is not a UPS device", "OBJ_PATH", path); continue; } if (!std::get<bool>(properties["IsPresent"])) { // There is a UPS detected but it is not officially // "present" yet. Monitor it for state change. info("UPower device {OBJ_PATH} is not present", "OBJ_PATH", path); continue; } if (std::get<uint>(properties["State"]) == STATE_FULLY_CHARGED) { info("UPS is fully charged"); } else { info("UPS is not fully charged: {UPS_STATE}", "UPS_STATE", std::get<uint>(properties["State"])); server::Chassis::currentPowerStatus( PowerStatus::UninterruptiblePowerSupply); return false; } if (std::get<uint>(properties["BatteryLevel"]) == BATTERY_LVL_FULL) { info("UPS Battery Level is Full"); // Only one UPS per system, we've found it and it's all // good so exit function return true; } else { info("UPS Battery Level is Low: {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL", std::get<uint>(properties["BatteryLevel"])); server::Chassis::currentPowerStatus( PowerStatus::UninterruptiblePowerSupply); return false; } } catch (const sdbusplus::exception_t& e) { error("Error reading UPS property, error: {ERROR}, " "service: {SERVICE} path: {PATH}", "ERROR", e, "SERVICE", service, "PATH", path); throw; } } } return true; } bool Chassis::determineStatusOfPSUPower() { // Find all implementations of the PowerSystemInputs interface auto mapper = bus.new_method_call(ObjectMapper::default_service, ObjectMapper::instance_path, ObjectMapper::interface, "GetSubTree"); mapper.append("/", 0, std::vector<std::string>( {decoratorServer::PowerSystemInputs::interface})); std::map<std::string, std::map<std::string, std::vector<std::string>>> mapperResponse; try { auto mapperResponseMsg = bus.call(mapper); mapperResponseMsg.read(mapperResponse); } catch (const sdbusplus::exception_t& e) { error("Error in mapper GetSubTree call for PowerSystemInputs: {ERROR}", "ERROR", e); throw; } for (const auto& [path, services] : mapperResponse) { for (const auto& serviceIter : services) { const std::string& service = serviceIter.first; try { auto method = bus.new_method_call(service.c_str(), path.c_str(), PROPERTY_INTERFACE, "GetAll"); method.append(decoratorServer::PowerSystemInputs::interface); auto response = bus.call(method); using Property = std::string; using Value = std::variant<std::string>; using PropertyMap = std::map<Property, Value>; PropertyMap properties; response.read(properties); auto statusStr = std::get<std::string>(properties["Status"]); auto status = decoratorServer::PowerSystemInputs::convertStatusFromString( statusStr); if (status == decoratorServer::PowerSystemInputs::Status::Fault) { info("Power System Inputs status is in Fault state"); server::Chassis::currentPowerStatus(PowerStatus::BrownOut); return false; } } catch (const sdbusplus::exception_t& e) { error( "Error reading Power System Inputs property, error: {ERROR}, " "service: {SERVICE} path: {PATH}", "ERROR", e, "SERVICE", service, "PATH", path); throw; } } } return true; } void Chassis::uPowerChangeEvent(sdbusplus::message_t& msg) { debug("UPS Property Change Event Triggered"); std::string statusInterface; std::map<std::string, std::variant<uint, bool>> msgData; msg.read(statusInterface, msgData); // If the change is to any of the properties we are interested in, then call // determineStatusOfPower(), which looks at all the power-related // interfaces, to see if a power status change is needed auto propertyMap = msgData.find("IsPresent"); if (propertyMap != msgData.end()) { info("UPS presence changed to {UPS_PRES_INFO}", "UPS_PRES_INFO", std::get<bool>(propertyMap->second)); determineStatusOfPower(); return; } propertyMap = msgData.find("State"); if (propertyMap != msgData.end()) { info("UPS State changed to {UPS_STATE}", "UPS_STATE", std::get<uint>(propertyMap->second)); determineStatusOfPower(); return; } propertyMap = msgData.find("BatteryLevel"); if (propertyMap != msgData.end()) { info("UPS BatteryLevel changed to {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL", std::get<uint>(propertyMap->second)); determineStatusOfPower(); return; } return; } void Chassis::powerSysInputsChangeEvent(sdbusplus::message_t& msg) { debug("Power System Inputs Property Change Event Triggered"); std::string statusInterface; std::map<std::string, std::variant<std::string>> msgData; msg.read(statusInterface, msgData); // If the change is to any of the properties we are interested in, then call // determineStatusOfPower(), which looks at all the power-related // interfaces, to see if a power status change is needed auto propertyMap = msgData.find("Status"); if (propertyMap != msgData.end()) { info("Power System Inputs status changed to {POWER_SYS_INPUT_STATUS}", "POWER_SYS_INPUT_STATUS", std::get<std::string>(propertyMap->second)); determineStatusOfPower(); return; } return; } void Chassis::startUnit(const std::string& sysdUnit) { auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit"); method.append(sysdUnit); method.append("replace"); this->bus.call_noreply(method); return; } void Chassis::restartUnit(const std::string& sysdUnit) { auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "RestartUnit"); method.append(sysdUnit); method.append("replace"); this->bus.call_noreply(method); return; } bool Chassis::stateActive(const std::string& target) { std::variant<std::string> currentState; sdbusplus::message::object_path unitTargetPath; auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "GetUnit"); method.append(target); try { auto result = this->bus.call(method); result.read(unitTargetPath); } catch (const sdbusplus::exception_t& e) { error("Error in GetUnit call: {ERROR}", "ERROR", e); return false; } method = this->bus.new_method_call( SYSTEMD_SERVICE, static_cast<const std::string&>(unitTargetPath).c_str(), SYSTEMD_PROPERTY_IFACE, "Get"); method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); try { auto result = this->bus.call(method); result.read(currentState); } catch (const sdbusplus::exception_t& e) { error("Error in ActiveState Get: {ERROR}", "ERROR", e); return false; } const auto& currentStateStr = std::get<std::string>(currentState); return currentStateStr == ACTIVE_STATE || currentStateStr == ACTIVATING_STATE; } int Chassis::sysStateChange(sdbusplus::message_t& msg) { sdbusplus::message::object_path newStateObjPath; std::string newStateUnit{}; std::string newStateResult{}; // Read the msg and populate each variable try { // newStateID is a throwaway that is needed in order to read the // parameters that are useful out of the dbus message uint32_t newStateID{}; msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); } catch (const sdbusplus::exception_t& e) { error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}", "ERROR", e, "REPLY_SIG", msg.get_signature()); return 0; } if ((newStateUnit == std::format(CHASSIS_STATE_POWEROFF_TGT_FMT, id)) && (newStateResult == "done") && (!stateActive(systemdTargetTable[Transition::On]))) { info("Received signal that power OFF is complete"); this->currentPowerState(server::Chassis::PowerState::Off); this->setStateChangeTime(); } else if ((newStateUnit == systemdTargetTable[Transition::On]) && (newStateResult == "done") && (stateActive(systemdTargetTable[Transition::On]))) { info("Received signal that power ON is complete"); this->currentPowerState(server::Chassis::PowerState::On); this->setStateChangeTime(); // Remove temporary file which is utilized for scenarios where the // BMC is rebooted while the chassis power is still on. // This file is used to indicate to chassis related systemd services // that the chassis is already on and they should skip running. // Once the chassis state is back to on we can clear this file. auto chassisFile = std::format(CHASSIS_ON_FILE, 0); if (std::filesystem::exists(chassisFile)) { std::filesystem::remove(chassisFile); } } return 0; } Chassis::Transition Chassis::requestedPowerTransition(Transition value) { info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}", "REQ_POWER_TRAN", value); #if ONLY_ALLOW_BOOT_WHEN_BMC_READY if ((value != Transition::Off) && (!utils::isBmcReady(this->bus))) { info("BMC State is not Ready so no chassis on operations allowed"); throw sdbusplus::xyz::openbmc_project::State::Chassis::Error:: BMCNotReady(); } #endif #ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION /* * Do not do transition when the any firmware being updated */ if ((value != Transition::Off) && (phosphor::state::manager::utils::isFirmwareUpdating(this->bus))) { info("Firmware being updated, reject the transition request"); throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable(); } #endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION startUnit(systemdTargetTable.find(value)->second); return server::Chassis::requestedPowerTransition(value); } Chassis::PowerState Chassis::currentPowerState(PowerState value) { PowerState chassisPowerState; info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE", value); chassisPowerState = server::Chassis::currentPowerState(value); if (chassisPowerState == PowerState::On) { pohTimer.resetRemaining(); } return chassisPowerState; } uint32_t Chassis::pohCounter(uint32_t value) { if (value != pohCounter()) { ChassisInherit::pohCounter(value); serializePOH(); } return pohCounter(); } void Chassis::pohCallback() { if (ChassisInherit::currentPowerState() == PowerState::On) { pohCounter(pohCounter() + 1); } } void Chassis::restorePOHCounter() { uint32_t counter; if (!deserializePOH(counter)) { // set to default value pohCounter(0); } else { pohCounter(counter); } } fs::path Chassis::serializePOH() { fs::path path{std::format(POH_COUNTER_PERSIST_PATH, id)}; std::ofstream os(path.c_str(), std::ios::binary); cereal::JSONOutputArchive oarchive(os); oarchive(pohCounter()); return path; } bool Chassis::deserializePOH(uint32_t& pohCounter) { fs::path path{std::format(POH_COUNTER_PERSIST_PATH, id)}; try { if (fs::exists(path)) { std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); cereal::JSONInputArchive iarchive(is); iarchive(pohCounter); return true; } return false; } catch (const cereal::Exception& e) { error("deserialize exception: {ERROR}", "ERROR", e); fs::remove(path); return false; } catch (const fs::filesystem_error& e) { return false; } return false; } void Chassis::startPOHCounter() { auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path(); fs::create_directories(dir); try { auto event = sdeventplus::Event::get_default(); bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); event.loop(); } catch (const sdeventplus::SdEventError& e) { error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR", e); phosphor::logging::commit<InternalFailure>(); } } void Chassis::serializeStateChangeTime() { fs::path path{std::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)}; std::ofstream os(path.c_str(), std::ios::binary); cereal::JSONOutputArchive oarchive(os); oarchive(ChassisInherit::lastStateChangeTime(), ChassisInherit::currentPowerState()); } bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state) { fs::path path{std::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)}; try { if (fs::exists(path)) { std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); cereal::JSONInputArchive iarchive(is); iarchive(time, state); return true; } } catch (const std::exception& e) { error("deserialize exception: {ERROR}", "ERROR", e); fs::remove(path); } return false; } void Chassis::restoreChassisStateChangeTime() { uint64_t time; PowerState state; if (!deserializeStateChangeTime(time, state)) { ChassisInherit::lastStateChangeTime(0); } else { ChassisInherit::lastStateChangeTime(time); } } void Chassis::setStateChangeTime() { using namespace std::chrono; uint64_t lastTime; PowerState lastState; auto now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()) .count(); // If power is on when the BMC is rebooted, this function will get called // because sysStateChange() runs. Since the power state didn't change // in this case, neither should the state change time, so check that // the power state actually did change here. if (deserializeStateChangeTime(lastTime, lastState)) { if (lastState == ChassisInherit::currentPowerState()) { return; } } ChassisInherit::lastStateChangeTime(now); serializeStateChangeTime(); } bool Chassis::standbyVoltageRegulatorFault() { bool regulatorFault = false; // find standby voltage regulator fault via gpiog auto gpioval = utils::getGpioValue("regulator-standby-faulted"); if (-1 == gpioval) { error("Failed reading regulator-standby-faulted GPIO"); } if (1 == gpioval) { info("Detected standby voltage regulator fault"); regulatorFault = true; } return regulatorFault; } } // namespace manager } // namespace state } // namespace phosphor