blob: 324270b199b6afd54745955c7d5143fdd450b065 [file] [log] [blame]
#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