blob: 7ccc6d948d5681617da2a07027e3bae1387cae2f [file] [log] [blame]
#include "occ_status.hpp"
#include <phosphor-logging/lg2.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";
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
{
lg2::error(
"updatePcapBounds: unable to find pcap_min_soft file: {FILE} (errno={ERR})",
"FILE", pcapBasePathname, "ERR", errno);
}
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
{
lg2::error(
"updatePcapBounds: unable to find cap_min file: {FILE} (errno={ERR})",
"FILE", pcapBasePathname, "ERR", errno);
}
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
{
lg2::error(
"updatePcapBounds: unable to find cap_max file: {FILE} (errno={ERR})",
"FILE", pcapBasePathname, "ERR", errno);
}
// 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
lg2::error(
"updatePcapBounds: user powercap mismatch (hwmon:{HCAP}W, bdus:{DCAP}W) - using dbus",
"HCAP", hwmonUserCap, "DCAP", dbusUserCap);
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;
}
lg2::error(
"updatePcapBounds: user powercap {CAP}W is outside bounds "
"(soft min:{SMIN}, min:{MIN}, max:{MAX})",
"CAP", dbusUserCap, "SMIN", capSoftMin, "MIN", capHardMin,
"MAX", capMax);
try
{
lg2::info(
"updatePcapBounds: Updating user powercap from {OLD} to {NEW}W",
"OLD", hwmonUserCap, "NEW", newCap);
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP,
newCap);
auto occInput = getOccInput(newCap, pcapEnabled);
writeOcc(occInput);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error(
"updatePcapBounds: Failed to update user powercap due to {ERR}",
"ERR", e.what());
}
}
}
}
// 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)
{
lg2::error("Failed to get PowerCap property: path={PATH}: {ERR}",
"PATH", PCAP_PATH, "ERR", e.what());
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)
{
lg2::error("Failed to get PowerCapEnable property: path={PATH}: {ERR}",
"PATH", PCAP_PATH, "ERR", e.what());
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
{
lg2::error("Power Cap base filename not found: {FILE}", "FILE",
pcapBasePathname);
}
// 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())
{
lg2::error("Could not find a power cap file to write to: {FILE})",
"FILE", pcapBasePathname);
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)
{
lg2::info("Writing {CAP}uW to {FILE}", "CAP", pcapString, "FILE",
fileName);
file << pcapString;
file.close();
}
else
{
lg2::error("Failed writing {CAP}uW to {FILE} (errno={ERR})", "CAP",
microWatts, "FILE", fileName, "ERR", errno);
}
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())
{
lg2::error(
"readUserCapHwmon: Could not find a power cap file to read: {FILE})",
"FILE", pcapBasePathname);
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
{
lg2::error("readUserCapHwmon: Failed reading {FILE} (errno={ERR})",
"FILE", userCapName, "ERR", errno);
}
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
lg2::debug(
"pcapChanged: Unknown power cap property changed {PROP} to {VAL}",
"PROP", prop, "VAL", std::get<uint32_t>(value));
}
}
// Validate the cap is within supported range
uint32_t capSoftMin, capHardMin, capMax;
readDbusPcapLimits(capSoftMin, capHardMin, capMax);
if (((pcap > 0) && (pcap < capSoftMin)) || ((pcap == 0) && (pcapEnabled)))
{
lg2::error(
"pcapChanged: Power cap of {CAP}W is lower than allowed (soft min:{SMIN}, min:{MIN}) - using soft min",
"CAP", pcap, "SMIN", capSoftMin, "MIN", capHardMin);
pcap = capSoftMin;
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap);
}
else if (pcap > capMax)
{
lg2::error(
"pcapChanged: Power cap of {CAP}W is higher than allowed (max:{MAX}) - using max",
"CAP", pcap, "MAX", capMax);
pcap = capMax;
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap);
}
if (changeFound)
{
lg2::info(
"Power Cap Property Change (cap={CAP}W (input), enabled={ENABLE})",
"CAP", pcap, "ENABLE", pcapEnabled);
// 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)
{
lg2::error(
"updateDbusPcapLimits: Failed to set SOFT PCAP to {MIN}W due to {ERR}",
"MIN", softMin, "ERR", e.what());
complete = false;
}
try
{
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN,
hardMin);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error(
"updateDbusPcapLimits: Failed to set HARD PCAP to {MIN}W due to {ERR}",
"MIN", hardMin, "ERR", e.what());
complete = false;
}
try
{
utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max);
}
catch (const sdbusplus::exception_t& e)
{
lg2::error(
"updateDbusPcapLimits: Failed to set MAX PCAP to {MAX}W due to {ERR}",
"MAX", max, "ERR", e.what());
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)
{
lg2::error("readDbusPcapLimits: Failed to get SOFT PCAP due to {ERR}",
"ERR", e.what());
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)
{
lg2::error("readDbusPcapLimits: Failed to get HARD PCAP due to {ERR}",
"ERR", e.what());
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)
{
lg2::error("readDbusPcapLimits: Failed to get MAX PCAP due to {ERR}",
"ERR", e.what());
max = INT_MAX;
complete = false;
}
return complete;
}
} // namespace powercap
} // namespace occ
} // namespace open_power