blob: 6a9c67f98204f17e7db05e684d9037d8b5de8b96 [file] [log] [blame]
#include "occ_status.hpp"
#include <fmt/core.h>
#include <phosphor-logging/log.hpp>
#include <powercap.hpp>
#include <cassert>
#include <filesystem>
namespace open_power
{
namespace occ
{
namespace powercap
{
constexpr auto PCAP_PATH = "/xyz/openbmc_project/control/host0/power_cap";
constexpr auto PCAP_INTERFACE = "xyz.openbmc_project.Control.Power.Cap";
constexpr auto POWER_CAP_PROP = "PowerCap";
constexpr auto POWER_CAP_ENABLE_PROP = "PowerCapEnable";
constexpr auto POWER_CAP_SOFT_MIN = "MinSoftPowerCapValue";
constexpr auto POWER_CAP_HARD_MIN = "MinPowerCapValue";
constexpr auto POWER_CAP_MAX = "MaxPowerCapValue";
using namespace phosphor::logging;
namespace fs = std::filesystem;
void PowerCap::updatePcapBounds()
{
// Build the hwmon string to write the power cap bounds
fs::path minName = getPcapFilename(std::regex{"power\\d+_cap_min$"});
fs::path softMinName =
getPcapFilename(std::regex{"power\\d+_cap_min_soft$"});
fs::path maxName = getPcapFilename(std::regex{"power\\d+_cap_max$"});
// Read the current cap bounds from dbus
uint32_t capSoftMin, capHardMin, capMax;
readDbusPcapLimits(capSoftMin, capHardMin, capMax);
// Read the power cap bounds from sysfs files (from OCC)
uint64_t cap;
std::ifstream softMinFile(softMinName, std::ios::in);
if (softMinFile)
{
softMinFile >> cap;
softMinFile.close();
// Convert to input/AC Power in Watts (round up)
capSoftMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
}
else
{
log<level::ERR>(
fmt::format(
"updatePcapBounds: unable to find pcap_min_soft file: {} (errno={})",
pcapBasePathname.c_str(), errno)
.c_str());
}
std::ifstream minFile(minName, std::ios::in);
if (minFile)
{
minFile >> cap;
minFile.close();
// Convert to input/AC Power in Watts (round up)
capHardMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9);
}
else
{
log<level::ERR>(
fmt::format(
"updatePcapBounds: unable to find cap_min file: {} (errno={})",
pcapBasePathname.c_str(), errno)
.c_str());
}
std::ifstream maxFile(maxName, std::ios::in);
if (maxFile)
{
maxFile >> cap;
maxFile.close();
// Convert to input/AC Power in Watts (truncate remainder)
capMax = cap / (PS_DERATING_FACTOR / 100.0) / 1000000;
}
else
{
log<level::ERR>(
fmt::format(
"updatePcapBounds: unable to find cap_max file: {} (errno={})",
pcapBasePathname.c_str(), errno)
.c_str());
}
// Save the power cap bounds to dbus
updateDbusPcapLimits(capSoftMin, capHardMin, capMax);
// Validate user power cap (if enabled) is within the bounds
const uint32_t dbusUserCap = getPcap();
const bool pcapEnabled = getPcapEnabled();
if (pcapEnabled && (dbusUserCap != 0))
{
const uint32_t hwmonUserCap = readUserCapHwmon();
if ((dbusUserCap >= capSoftMin) && (dbusUserCap <= capMax))
{
// Validate dbus and hwmon user caps match
if ((hwmonUserCap != 0) && (dbusUserCap != hwmonUserCap))
{
// User power cap is enabled, but does not match dbus
log<level::ERR>(
fmt::format(
"updatePcapBounds: user powercap mismatch (hwmon:{}W, bdus:{}W) - using dbus",
hwmonUserCap, dbusUserCap)
.c_str());
auto occInput = getOccInput(dbusUserCap, pcapEnabled);
writeOcc(occInput);
}
}
else
{
// User power cap is outside of current bounds
uint32_t newCap = capMax;
if (dbusUserCap < capSoftMin)
{
newCap = capSoftMin;
}
log<level::ERR>(
fmt::format(
"updatePcapBounds: user powercap {}W is outside bounds "
"(soft min:{}, min:{}, max:{})",
dbusUserCap, capSoftMin, capHardMin, capMax)
.c_str());
try
{
log<level::INFO>(
fmt::format(
"updatePcapBounds: Updating user powercap from {} to {}W",
hwmonUserCap, newCap)
.c_str());
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP,
newCap);
auto occInput = getOccInput(newCap, pcapEnabled);
writeOcc(occInput);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format(
"updatePcapBounds: Failed to update user powercap due to ",
e.what())
.c_str());
}
}
}
}
// Get value of power cap to send to the OCC (output/DC power)
uint32_t PowerCap::getOccInput(uint32_t pcap, bool pcapEnabled)
{
if (!pcapEnabled)
{
// Pcap disabled, return 0 to indicate disabled
return 0;
}
// If pcap is not disabled then just return the pcap with the derating
// factor applied (output/DC power).
return ((static_cast<uint64_t>(pcap) * PS_DERATING_FACTOR) / 100);
}
uint32_t PowerCap::getPcap()
{
utils::PropertyValue pcap{};
try
{
pcap = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP);
return std::get<uint32_t>(pcap);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>("Failed to get PowerCap property",
entry("ERROR=%s", e.what()),
entry("PATH=%s", PCAP_PATH));
return 0;
}
}
bool PowerCap::getPcapEnabled()
{
utils::PropertyValue pcapEnabled{};
try
{
pcapEnabled = utils::getProperty(PCAP_PATH, PCAP_INTERFACE,
POWER_CAP_ENABLE_PROP);
return std::get<bool>(pcapEnabled);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>("Failed to get PowerCapEnable property",
entry("ERROR=%s", e.what()),
entry("PATH=%s", PCAP_PATH));
return false;
}
}
fs::path PowerCap::getPcapFilename(const std::regex& expr)
{
if (pcapBasePathname.empty())
{
pcapBasePathname = occStatus.getHwmonPath();
}
if (fs::exists(pcapBasePathname))
{
// Search for pcap file based on the supplied expr
for (auto& file : fs::directory_iterator(pcapBasePathname))
{
if (std::regex_search(file.path().string(), expr))
{
// Found match
return file;
}
}
}
else
{
log<level::ERR>(fmt::format("Power Cap base filename not found: {}",
pcapBasePathname.c_str())
.c_str());
}
// return empty path
return fs::path{};
}
// Write the user power cap to sysfs (output/DC power)
// This will trigger the driver to send the cap to the OCC
void PowerCap::writeOcc(uint32_t pcapValue)
{
if (!occStatus.occActive())
{
// OCC not running, skip update
return;
}
// Build the hwmon string to write the user power cap
fs::path fileName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
if (fileName.empty())
{
log<level::ERR>(
fmt::format("Could not find a power cap file to write to: {})",
pcapBasePathname.c_str())
.c_str());
return;
}
uint64_t microWatts = pcapValue * 1000000ull;
auto pcapString{std::to_string(microWatts)};
// Open the hwmon file and write the power cap
std::ofstream file(fileName, std::ios::out);
if (file)
{
log<level::INFO>(fmt::format("Writing {}uW to {}", pcapString.c_str(),
fileName.c_str())
.c_str());
file << pcapString;
file.close();
}
else
{
log<level::ERR>(fmt::format("Failed writing {}uW to {} (errno={})",
microWatts, fileName.c_str(), errno)
.c_str());
}
return;
}
// Read the current user power cap from sysfs as input/AC power
uint32_t PowerCap::readUserCapHwmon()
{
uint32_t userCap = 0;
// Get the sysfs filename for the user power cap
fs::path userCapName = getPcapFilename(std::regex{"power\\d+_cap_user$"});
if (userCapName.empty())
{
log<level::ERR>(
fmt::format(
"readUserCapHwmon: Could not find a power cap file to read: {})",
pcapBasePathname.c_str())
.c_str());
return 0;
}
// Open the sysfs file and read the power cap
std::ifstream file(userCapName, std::ios::in);
if (file)
{
uint64_t cap;
file >> cap;
file.close();
// Convert to input/AC Power in Watts
userCap = (cap / (PS_DERATING_FACTOR / 100.0) / 1000000);
}
else
{
log<level::ERR>(
fmt::format("readUserCapHwmon: Failed reading {} (errno={})",
userCapName.c_str(), errno)
.c_str());
}
return userCap;
}
void PowerCap::pcapChanged(sdbusplus::message_t& msg)
{
if (!occStatus.occActive())
{
// Nothing to do
return;
}
uint32_t pcap = 0;
bool pcapEnabled = false;
std::string msgSensor;
std::map<std::string, std::variant<uint32_t, bool>> msgData;
msg.read(msgSensor, msgData);
bool changeFound = false;
for (const auto& [prop, value] : msgData)
{
if (prop == POWER_CAP_PROP)
{
pcap = std::get<uint32_t>(value);
pcapEnabled = getPcapEnabled();
changeFound = true;
}
else if (prop == POWER_CAP_ENABLE_PROP)
{
pcapEnabled = std::get<bool>(value);
pcap = getPcap();
changeFound = true;
}
else
{
// Ignore other properties
log<level::DEBUG>(
fmt::format(
"pcapChanged: Unknown power cap property changed {} to {}",
prop.c_str(), std::get<uint32_t>(value))
.c_str());
}
}
// Validate the cap is within supported range
uint32_t capSoftMin, capHardMin, capMax;
readDbusPcapLimits(capSoftMin, capHardMin, capMax);
if (((pcap > 0) && (pcap < capSoftMin)) || ((pcap == 0) && (pcapEnabled)))
{
log<level::ERR>(
fmt::format(
"pcapChanged: Power cap of {}W is lower than allowed (soft min:{}, min:{}) - using soft min",
pcap, capSoftMin, capHardMin)
.c_str());
pcap = capSoftMin;
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap);
}
else if (pcap > capMax)
{
log<level::ERR>(
fmt::format(
"pcapChanged: Power cap of {}W is higher than allowed (max:{}) - using max",
pcap, capSoftMin, capHardMin)
.c_str());
pcap = capMax;
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap);
}
if (changeFound)
{
log<level::INFO>(
fmt::format(
"Power Cap Property Change (cap={}W (input), enabled={})", pcap,
pcapEnabled ? 'y' : 'n')
.c_str());
// Determine desired action to write to occ
auto occInput = getOccInput(pcap, pcapEnabled);
// Write action to occ
writeOcc(occInput);
}
return;
}
// Update the Power Cap bounds on DBus
bool PowerCap::updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin,
uint32_t max)
{
bool complete = true;
try
{
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN,
softMin);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format(
"updateDbusPcapLimits: Failed to set SOFT PCAP to {}W due to {}",
softMin, e.what())
.c_str());
complete = false;
}
try
{
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
hardMin);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format(
"updateDbusPcapLimits: Failed to set HARD PCAP to {}W due to {}",
hardMin, e.what())
.c_str());
complete = false;
}
try
{
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format(
"updateDbusPcapLimits: Failed to set MAX PCAP to {}W due to {}",
max, e.what())
.c_str());
complete = false;
}
return complete;
}
// Read the Power Cap bounds from DBus
bool PowerCap::readDbusPcapLimits(uint32_t& softMin, uint32_t& hardMin,
uint32_t& max)
{
bool complete = true;
utils::PropertyValue prop{};
try
{
prop =
utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN);
softMin = std::get<uint32_t>(prop);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format("readDbusPcapLimits: Failed to get SOFT PCAP due to {}",
e.what())
.c_str());
softMin = 0;
complete = false;
}
try
{
prop =
utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN);
hardMin = std::get<uint32_t>(prop);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format("readDbusPcapLimits: Failed to get HARD PCAP due to {}",
e.what())
.c_str());
hardMin = 0;
complete = false;
}
try
{
prop = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX);
max = std::get<uint32_t>(prop);
}
catch (const sdbusplus::exception_t& e)
{
log<level::ERR>(
fmt::format("readDbusPcapLimits: Failed to get MAX PCAP due to {}",
e.what())
.c_str());
max = INT_MAX;
complete = false;
}
return complete;
}
} // namespace powercap
} // namespace occ
} // namespace open_power