blob: f5b4735b72e29f1cc99566e546b0a2ca6dae8c3a [file] [log] [blame]
/**
* Copyright © 2021 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "power_control.hpp"
#include "chassis.hpp"
#include "config_file_parser.hpp"
#include "format_utils.hpp"
#include "types.hpp"
#include "utility.hpp"
#include <exception>
#include <format>
#include <functional>
#include <span>
#include <stdexcept>
#include <thread>
#include <utility>
namespace phosphor::power::sequencer
{
const std::string powerOnTimeoutError =
"xyz.openbmc_project.Power.Error.PowerOnTimeout";
const std::string powerOffTimeoutError =
"xyz.openbmc_project.Power.Error.PowerOffTimeout";
const std::string shutdownError = "xyz.openbmc_project.Power.Error.Shutdown";
PowerControl::PowerControl(sdbusplus::bus_t& bus,
const sdeventplus::Event& event) :
PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit}, bus{bus},
services{bus},
pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)},
powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime},
timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval}
{
// Obtain dbus service name
bus.request_name(POWER_IFACE);
compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>(
bus, std::bind_front(&PowerControl::compatibleSystemTypesFound, this));
setUpGpio();
}
int PowerControl::getPgood() const
{
return pgood;
}
int PowerControl::getPgoodTimeout() const
{
return timeout.count();
}
int PowerControl::getState() const
{
return state;
}
void PowerControl::onFailureCallback()
{
services.logInfoMsg("After onFailure wait");
onFailure(false);
// Power good has failed, call for chassis hard power off
auto method = bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT,
util::SYSTEMD_INTERFACE, "StartUnit");
method.append(util::POWEROFF_TARGET);
method.append("replace");
bus.call_noreply(method);
}
void PowerControl::onFailure(bool wasTimeOut)
{
std::string error;
std::map<std::string, std::string> additionalData{};
// Check if pgood fault occurred on one of the rails being monitored
error = findPgoodFault(additionalData);
// If fault was not isolated to a voltage rail, select a more generic error
if (error.empty())
{
if (!powerSupplyError.empty())
{
error = powerSupplyError;
}
else if (wasTimeOut)
{
error = powerOnTimeoutError;
}
else
{
error = shutdownError;
}
}
services.logError(error, Entry::Level::Critical, additionalData);
if (!wasTimeOut)
{
services.createBMCDump();
}
}
std::string PowerControl::findPgoodFault(
std::map<std::string, std::string>& additionalData)
{
// Note: This code is temporary. It will be replaced when additional
// multi-chassis support is implementated in this application.
std::string error{};
if (system)
{
try
{
for (auto& chassis : system->getChassis())
{
for (auto& powerSequencer : chassis->getPowerSequencers())
{
error = powerSequencer->findPgoodFault(
services, powerSupplyError, additionalData);
if (!error.empty())
{
return error;
}
}
}
}
catch (const std::exception& e)
{
services.logErrorMsg(e.what());
additionalData.emplace("ERROR", e.what());
}
}
return error;
}
void PowerControl::pollPgood()
{
if (inStateTransition)
{
// In transition between power on and off, check for timeout
const auto now = std::chrono::steady_clock::now();
if (now > pgoodTimeoutTime)
{
services.logErrorMsg(std::format(
"Power state transition timeout, state: {}", state));
inStateTransition = false;
if (state)
{
// Time out powering on
onFailure(true);
}
else
{
// Time out powering off
std::map<std::string, std::string> additionalData{};
services.logError(powerOffTimeoutError, Entry::Level::Critical,
additionalData);
}
failureFound = true;
return;
}
}
int pgoodState = pgoodLine.get_value();
if (pgoodState != pgood)
{
// Power good has changed since last read
pgood = pgoodState;
if (pgoodState == 0)
{
emitPowerLostSignal();
}
else
{
emitPowerGoodSignal();
// Clear any errors on the transition to power on
powerSupplyError.clear();
failureFound = false;
}
emitPropertyChangedSignal("pgood");
}
if (pgoodState == state)
{
// Power good matches requested state
inStateTransition = false;
}
else if (!inStateTransition && (pgoodState == 0) && !failureFound)
{
// Not in power off state, not changing state, and power good is off
services.logErrorMsg("Chassis pgood failure");
pgoodWaitTimer.restartOnce(std::chrono::seconds(7));
failureFound = true;
}
}
void PowerControl::setPgoodTimeout(int t)
{
if (timeout.count() != t)
{
timeout = std::chrono::seconds(t);
emitPropertyChangedSignal("pgood_timeout");
}
}
void PowerControl::setPowerSupplyError(const std::string& error)
{
powerSupplyError = error;
}
void PowerControl::setState(int s)
{
if (state == s)
{
services.logInfoMsg(
std::format("Power already at requested state: {}", state));
return;
}
if (s == 0)
{
// Wait for two seconds when powering down. This is to allow host and
// other BMC applications time to complete power off processing
std::this_thread::sleep_for(std::chrono::seconds(2));
}
else
{
// If minimum power off time has not passed, wait
if (powerOnAllowedTime > std::chrono::steady_clock::now())
{
services.logInfoMsg(std::format(
"Waiting {} seconds until power on allowed",
std::chrono::duration_cast<std::chrono::seconds>(
powerOnAllowedTime - std::chrono::steady_clock::now())
.count()));
}
std::this_thread::sleep_until(powerOnAllowedTime);
}
services.logInfoMsg(std::format("setState: {}", s));
services.logInfoMsg(std::format("Powering chassis {}", (s ? "on" : "off")));
powerControlLine.request(
{"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0});
powerControlLine.set_value(s);
powerControlLine.release();
if (s == 0)
{
// Set a minimum amount of time to wait before next power on
powerOnAllowedTime =
std::chrono::steady_clock::now() + minimumPowerOffTime;
}
pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout;
inStateTransition = true;
state = s;
emitPropertyChangedSignal("state");
}
void PowerControl::compatibleSystemTypesFound(
const std::vector<std::string>& types)
{
// If we don't already have compatible system types
if (compatibleSystemTypes.empty())
{
std::string typesStr = format_utils::toString(std::span{types});
services.logInfoMsg(
std::format("Compatible system types found: {}", typesStr));
// Store compatible system types
compatibleSystemTypes = types;
// Load config file that matches one of the compatible system types
loadConfigFile();
}
}
void PowerControl::setUpGpio()
{
const std::string powerControlLineName = "power-chassis-control";
const std::string pgoodLineName = "power-chassis-good";
pgoodLine = gpiod::find_line(pgoodLineName);
if (!pgoodLine)
{
std::string errorString{"GPIO line name not found: " + pgoodLineName};
services.logErrorMsg(errorString);
throw std::runtime_error(errorString);
}
powerControlLine = gpiod::find_line(powerControlLineName);
if (!powerControlLine)
{
std::string errorString{
"GPIO line name not found: " + powerControlLineName};
services.logErrorMsg(errorString);
throw std::runtime_error(errorString);
}
pgoodLine.request(
{"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0});
int pgoodState = pgoodLine.get_value();
pgood = pgoodState;
state = pgoodState;
services.logInfoMsg(std::format("Pgood state: {}", pgoodState));
}
void PowerControl::loadConfigFile()
{
try
{
std::filesystem::path configFile = findConfigFile();
if (!configFile.empty())
{
std::vector<std::unique_ptr<Chassis>> chassis =
config_file_parser::parse(configFile, services);
system = std::make_unique<System>(std::move(chassis));
}
}
catch (const std::exception& e)
{
services.logErrorMsg(std::format(
"Unable to parse JSON configuration file: {}", e.what()));
}
}
std::filesystem::path PowerControl::findConfigFile()
{
// Find config file for current system based on compatible system types
std::filesystem::path configFile;
if (!compatibleSystemTypes.empty())
{
try
{
configFile = config_file_parser::find(compatibleSystemTypes);
if (!configFile.empty())
{
services.logInfoMsg(std::format(
"JSON configuration file found: {}", configFile.string()));
}
}
catch (const std::exception& e)
{
services.logErrorMsg(std::format(
"Unable to find JSON configuration file: {}", e.what()));
}
}
return configFile;
}
} // namespace phosphor::power::sequencer