Add support for OEM Power Modes
- Allow mode to be set via PassThrough interface
- Allow non-customer OEM power modes to be persisted
- Persist any OEM power mode settings
- moved mode related code from Status to PowerMode object
- merged PowerIPS into PowerMode object
Tested on Everest and Rainier.
Setting mode through PassThrough/ce-login:
busctl call org.open_power.OCC.Control /org/open_power/control/occ0 org.open_power.OCC.PassThrough SetMode yq 11 3600
Trace (via PassThrough interface)
openpower-occ-control[4440]: PassThrough::setMode() Setting Power Mode 11 (data: 3600)
openpower-occ-control[4440]: PowerMode::sendModeChange: SET_MODE(11,3600) command to OCC0 (9 bytes)
Trace (setting mode via GUI/Redfish):
openpower-occ-control[4440]: Power Mode Change Requested: xyz.openbmc_project.Control.Power.Mode.PowerMode.MaximumPerformance
openpower-occ-control[4440]: PowerMode::sendModeChange: SET_MODE(12,0) command to OCC0 (9 bytes)
Verified when system in any OEM mode that Redfish also reports OEM
Verified all modes are persisted across PM Complex resets and reboots
Change-Id: Idd0be05cb6fd74dbd0776145f212c49addd1c365
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/powermode.cpp b/powermode.cpp
index e87b921..04a9787 100644
--- a/powermode.cpp
+++ b/powermode.cpp
@@ -1,10 +1,13 @@
+#include "powermode.hpp"
+
#include <fmt/core.h>
+#include <com/ibm/Host/Target/server.hpp>
#include <phosphor-logging/log.hpp>
-#include <powermode.hpp>
#include <xyz/openbmc_project/Control/Power/Mode/server.hpp>
#include <cassert>
+#include <fstream>
#include <regex>
namespace open_power
@@ -17,13 +20,10 @@
using namespace phosphor::logging;
using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode;
+// Called when DBus power mode gets changed
void PowerMode::modeChanged(sdbusplus::message::message& msg)
{
- if (!occStatus.occActive())
- {
- // Nothing to do
- return;
- }
+ SysPwrMode newMode = SysPwrMode::NO_CHANGE;
std::map<std::string, std::variant<std::string>> properties{};
std::string interface;
@@ -34,22 +34,51 @@
{
auto modeEntryValue = modeEntry->second;
propVal = std::get<std::string>(modeEntryValue);
-
- if (convertStringToMode(propVal) != SysPwrMode::NO_CHANGE)
+ newMode = convertStringToMode(propVal);
+ if (newMode != SysPwrMode::NO_CHANGE)
{
+ // DBus mode changed, get rid of any OEM mode if set
+ persistedData.purge();
+
log<level::INFO>(
fmt::format("Power Mode Change Requested: {}", propVal)
.c_str());
- // Trigger mode change to OCC
- occStatus.sendModeChange();
+ // Send mode change to OCC
+ sendModeChange();
+ }
+ }
+}
+
+// Called from OCC PassThrough interface (via CE login / BMC command line)
+bool PowerMode::setMode(const SysPwrMode newMode, const uint16_t modeData)
+{
+ if (updateDbusMode(newMode) == false)
+ {
+ // Unsupported mode
+ return false;
+ }
+
+ // If new mode is valid customer mode, the DBus update will trigger the mode
+ // change request to OCC. For OEM modes, the request will be sent here.
+ if (VALID_OEM_POWER_MODE_SETTING(newMode))
+ {
+ // Save OEM mode
+ persistedData.writeModeFile(newMode, modeData);
+
+ // Send mode change to OCC
+ if (sendModeChange() != CmdStatus::SUCCESS)
+ {
+ // Mode change failed
+ return false;
}
}
- return;
+ return true;
}
// Convert PowerMode string to OCC SysPwrMode
+// Returns NO_CHANGE if OEM or unsupported mode
SysPwrMode convertStringToMode(const std::string& i_modeString)
{
SysPwrMode pmode = SysPwrMode::NO_CHANGE;
@@ -65,22 +94,230 @@
}
else if (mode == Mode::PowerMode::Static)
{
- pmode = SysPwrMode::DISABLE;
+ pmode = SysPwrMode::STATIC;
}
else
{
- log<level::ERR>(
- fmt::format("convertStringToMode: Invalid Power Mode specified: {}",
- i_modeString)
- .c_str());
+ if (mode != Mode::PowerMode::OEM)
+ {
+ log<level::ERR>(
+ fmt::format(
+ "convertStringToMode: Invalid Power Mode specified: {}",
+ i_modeString)
+ .c_str());
+ }
}
return pmode;
}
-void PowerIPS::ipsChanged(sdbusplus::message::message& msg)
+// Check if Hypervisor target is PowerVM
+bool isPowerVM()
{
- if (!occStatus.occActive())
+ using namespace open_power::occ::powermode;
+ namespace Hyper = sdbusplus::com::ibm::Host::server;
+ constexpr auto HYPE_PATH = "/com/ibm/host0/hypervisor";
+ constexpr auto HYPE_INTERFACE = "com.ibm.Host.Target";
+ constexpr auto HYPE_PROP = "Target";
+
+ bool powerVmTarget = false;
+
+ // This will throw exception on failure
+ auto& bus = utils::getBus();
+ auto service = utils::getService(HYPE_PATH, HYPE_INTERFACE);
+ auto method = bus.new_method_call(service.c_str(), HYPE_PATH,
+ "org.freedesktop.DBus.Properties", "Get");
+ method.append(HYPE_INTERFACE, HYPE_PROP);
+ auto reply = bus.call(method);
+
+ std::variant<std::string> hyperEntryValue;
+ reply.read(hyperEntryValue);
+ auto propVal = std::get<std::string>(hyperEntryValue);
+ if (Hyper::Target::convertHypervisorFromString(propVal) ==
+ Hyper::Target::Hypervisor::PowerVM)
+ {
+ powerVmTarget = true;
+ }
+
+ log<level::DEBUG>(
+ fmt::format("isPowerVM returning {}", powerVmTarget).c_str());
+
+ return powerVmTarget;
+}
+
+// Get the requested power mode from DBus
+SysPwrMode PowerMode::getDbusMode()
+{
+ using namespace open_power::occ::powermode;
+ SysPwrMode currentMode = SysPwrMode::NO_CHANGE;
+
+ // This will throw exception on failure
+ auto& bus = utils::getBus();
+ auto service = utils::getService(PMODE_PATH, PMODE_INTERFACE);
+ auto method = bus.new_method_call(service.c_str(), PMODE_PATH,
+ "org.freedesktop.DBus.Properties", "Get");
+ method.append(PMODE_INTERFACE, POWER_MODE_PROP);
+ auto reply = bus.call(method);
+
+ std::variant<std::string> stateEntryValue;
+ reply.read(stateEntryValue);
+ auto propVal = std::get<std::string>(stateEntryValue);
+
+ currentMode = powermode::convertStringToMode(propVal);
+ if (!VALID_POWER_MODE_SETTING(currentMode))
+ {
+ log<level::ERR>(
+ fmt::format(
+ "PowerMode::getDbusMode Invalid power mode found on DBus: {}",
+ currentMode)
+ .c_str());
+ currentMode = SysPwrMode::NO_CHANGE;
+ }
+
+ return currentMode;
+}
+
+// Set the power mode on DBus
+bool PowerMode::updateDbusMode(const SysPwrMode newMode)
+{
+ using namespace open_power::occ::powermode;
+ using namespace std::literals::string_literals;
+
+ if (!VALID_POWER_MODE_SETTING(newMode) &&
+ !VALID_OEM_POWER_MODE_SETTING(newMode))
+ {
+ log<level::ERR>(
+ fmt::format(
+ "PowerMode::updateDbusMode - Requested power mode not supported: {}",
+ newMode)
+ .c_str());
+ return false;
+ }
+
+ // Mode::PowerMode dBusMode;
+ std::string dBusMode;
+ switch (newMode)
+ {
+ case SysPwrMode::STATIC:
+ dBusMode = PMODE_INTERFACE + ".PowerMode.Static"s;
+ break;
+ case SysPwrMode::POWER_SAVING:
+ dBusMode = PMODE_INTERFACE + ".PowerMode.PowerSaving"s;
+ break;
+ case SysPwrMode::MAX_PERF:
+ dBusMode = PMODE_INTERFACE + ".PowerMode.MaximumPerformance"s;
+ break;
+ default:
+ dBusMode = PMODE_INTERFACE + ".PowerMode.OEM"s;
+ }
+
+ utils::setProperty(PMODE_PATH, PMODE_INTERFACE, POWER_MODE_PROP,
+ std::move(dBusMode));
+
+ return true;
+}
+
+// Send mode change request to the master OCC
+CmdStatus PowerMode::sendModeChange()
+{
+ CmdStatus status = CmdStatus::FAILURE;
+
+ if (!masterActive)
+ {
+ // Nothing to do
+ log<level::DEBUG>("PowerMode::sendModeChange: MODE CHANGE not enabled");
+ return CmdStatus::SUCCESS;
+ }
+
+ if (!isPowerVM())
+ {
+ // Mode change is only supported on PowerVM systems
+ log<level::DEBUG>(
+ "PowerMode::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
+ return CmdStatus::SUCCESS;
+ }
+
+ // Use OEM power mode if it was set
+ SysPwrMode newMode = SysPwrMode::NO_CHANGE;
+ uint16_t modeData = 0;
+ if (persistedData.getOemMode(newMode, modeData) == false)
+ {
+ // Read customer power mode from Dbus
+ newMode = getDbusMode();
+ }
+
+ if (VALID_POWER_MODE_SETTING(newMode) ||
+ VALID_OEM_POWER_MODE_SETTING(newMode))
+ {
+ std::vector<std::uint8_t> cmd, rsp;
+ cmd.reserve(9);
+ cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
+ cmd.push_back(0x00); // Data Length (2 bytes)
+ cmd.push_back(0x06);
+ cmd.push_back(0x30); // Data (Version)
+ cmd.push_back(uint8_t(OccState::NO_CHANGE));
+ cmd.push_back(uint8_t(newMode));
+ cmd.push_back(modeData >> 8); // Mode Data (Freq Point)
+ cmd.push_back(modeData & 0xFF); //
+ cmd.push_back(0x00); // reserved
+ log<level::INFO>(
+ fmt::format(
+ "PowerMode::sendModeChange: SET_MODE({},{}) command to OCC{} ({} bytes)",
+ newMode, modeData, occInstance, cmd.size())
+ .c_str());
+ status = occCmd.send(cmd, rsp);
+ if (status == CmdStatus::SUCCESS)
+ {
+ if (rsp.size() == 5)
+ {
+ if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+ {
+ log<level::ERR>(
+ fmt::format(
+ "PowerMode::sendModeChange: SET MODE failed with status 0x{:02X}",
+ rsp[2])
+ .c_str());
+ dump_hex(rsp);
+ status = CmdStatus::FAILURE;
+ }
+ }
+ else
+ {
+ log<level::ERR>(
+ "PowerMode::sendModeChange: INVALID SET MODE response");
+ dump_hex(rsp);
+ status = CmdStatus::FAILURE;
+ }
+ }
+ else
+ {
+ if (status == CmdStatus::OPEN_FAILURE)
+ {
+ // OCC not active yet
+ status = CmdStatus::SUCCESS;
+ }
+ else
+ {
+ log<level::ERR>("PowerMode::sendModeChange: SET_MODE FAILED!");
+ }
+ }
+ }
+ else
+ {
+ log<level::ERR>(
+ fmt::format(
+ "PowerMode::sendModeChange: Unable to set power mode to {}",
+ newMode)
+ .c_str());
+ status = CmdStatus::FAILURE;
+ }
+
+ return status;
+}
+
+void PowerMode::ipsChanged(sdbusplus::message::message& msg)
+{
+ if (!masterActive)
{
// Nothing to do
return;
@@ -145,12 +382,308 @@
if (parmsChanged)
{
// Trigger mode change to OCC
- occStatus.sendIpsData();
+ sendIpsData();
}
return;
}
+/** @brief Get the Idle Power Saver properties
+ * @return true if IPS is enabled
+ */
+bool PowerMode::getIPSParms(uint8_t& enterUtil, uint16_t& enterTime,
+ uint8_t& exitUtil, uint16_t& exitTime)
+{
+ using namespace open_power::occ::powermode;
+ // Defaults:
+ bool ipsEnabled = false; // Disabled
+ enterUtil = 8; // Enter Utilization (8%)
+ enterTime = 240; // Enter Delay Time (240s)
+ exitUtil = 12; // Exit Utilization (12%)
+ exitTime = 10; // Exit Delay Time (10s)
+
+ std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
+ ipsProperties{};
+
+ // Get all IPS properties from DBus
+ try
+ {
+ auto& bus = utils::getBus();
+ auto service = utils::getService(PIPS_PATH, PIPS_INTERFACE);
+ auto method =
+ bus.new_method_call(service.c_str(), PIPS_PATH,
+ "org.freedesktop.DBus.Properties", "GetAll");
+ method.append(PIPS_INTERFACE);
+ auto reply = bus.call(method);
+ reply.read(ipsProperties);
+ }
+ catch (const sdbusplus::exception::exception& e)
+ {
+ log<level::ERR>(
+ fmt::format(
+ "Unable to read Idle Power Saver parameters so it will be disabled: {}",
+ e.what())
+ .c_str());
+ return ipsEnabled;
+ }
+
+ auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
+ if (ipsEntry != ipsProperties.end())
+ {
+ ipsEnabled = std::get<bool>(ipsEntry->second);
+ }
+ else
+ {
+ log<level::ERR>(
+ fmt::format("PowerMode::getIPSParms could not find property: {}",
+ IPS_ENABLED_PROP)
+ .c_str());
+ }
+
+ ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
+ if (ipsEntry != ipsProperties.end())
+ {
+ enterUtil = std::get<uint8_t>(ipsEntry->second);
+ }
+ else
+ {
+ log<level::ERR>(
+ fmt::format("PowerMode::getIPSParms could not find property: {}",
+ IPS_ENTER_UTIL)
+ .c_str());
+ }
+
+ ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
+ if (ipsEntry != ipsProperties.end())
+ {
+ std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+ enterTime =
+ std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+ }
+ else
+ {
+ log<level::ERR>(
+ fmt::format("PowerMode::getIPSParms could not find property: {}",
+ IPS_ENTER_TIME)
+ .c_str());
+ }
+
+ ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
+ if (ipsEntry != ipsProperties.end())
+ {
+ exitUtil = std::get<uint8_t>(ipsEntry->second);
+ }
+ else
+ {
+ log<level::ERR>(
+ fmt::format("PowerMode::getIPSParms could not find property: {}",
+ IPS_EXIT_UTIL)
+ .c_str());
+ }
+
+ ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
+ if (ipsEntry != ipsProperties.end())
+ {
+ std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+ exitTime = std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+ }
+ else
+ {
+ log<level::ERR>(
+ fmt::format("PowerMode::getIPSParms could not find property: {}",
+ IPS_EXIT_TIME)
+ .c_str());
+ }
+
+ if (enterUtil > exitUtil)
+ {
+ log<level::ERR>(
+ fmt::format(
+ "ERROR: Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
+ enterUtil, exitUtil)
+ .c_str());
+ enterUtil = exitUtil;
+ }
+
+ return ipsEnabled;
+}
+
+// Send Idle Power Saver config data to the master OCC
+CmdStatus PowerMode::sendIpsData()
+{
+ CmdStatus status = CmdStatus::FAILURE;
+
+ if (!masterActive)
+ {
+ // Nothing to do
+ return CmdStatus::SUCCESS;
+ }
+
+ if (!isPowerVM())
+ {
+ // Idle Power Saver data is only supported on PowerVM systems
+ log<level::DEBUG>(
+ "PowerMode::sendIpsData: SET_CFG_DATA[IPS] does not get sent on non-PowerVM systems");
+ return CmdStatus::SUCCESS;
+ }
+
+ uint8_t enterUtil, exitUtil;
+ uint16_t enterTime, exitTime;
+ const bool ipsEnabled =
+ getIPSParms(enterUtil, enterTime, exitUtil, exitTime);
+
+ log<level::INFO>(
+ fmt::format(
+ "Idle Power Saver Parameters: enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
+ ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
+ .c_str());
+
+ std::vector<std::uint8_t> cmd, rsp;
+ cmd.reserve(12);
+ cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
+ cmd.push_back(0x00); // Data Length (2 bytes)
+ cmd.push_back(0x09); //
+ cmd.push_back(0x11); // Config Format: IPS Settings
+ cmd.push_back(0x00); // Version
+ cmd.push_back(ipsEnabled ? 1 : 0); // IPS Enable
+ cmd.push_back(enterTime >> 8); // Enter Delay Time
+ cmd.push_back(enterTime & 0xFF); //
+ cmd.push_back(enterUtil); // Enter Utilization
+ cmd.push_back(exitTime >> 8); // Exit Delay Time
+ cmd.push_back(exitTime & 0xFF); //
+ cmd.push_back(exitUtil); // Exit Utilization
+ log<level::INFO>(fmt::format("PowerMode::sendIpsData: SET_CFG_DATA[IPS] "
+ "command to OCC{} ({} bytes)",
+ occInstance, cmd.size())
+ .c_str());
+ status = occCmd.send(cmd, rsp);
+ if (status == CmdStatus::SUCCESS)
+ {
+ if (rsp.size() == 5)
+ {
+ if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+ {
+ log<level::ERR>(
+ fmt::format(
+ "PowerMode::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
+ rsp[2])
+ .c_str());
+ dump_hex(rsp);
+ status = CmdStatus::FAILURE;
+ }
+ }
+ else
+ {
+ log<level::ERR>(
+ "PowerMode::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
+ dump_hex(rsp);
+ status = CmdStatus::FAILURE;
+ }
+ }
+ else
+ {
+ if (status == CmdStatus::OPEN_FAILURE)
+ {
+ // OCC not active yet
+ status = CmdStatus::SUCCESS;
+ }
+ else
+ {
+ log<level::ERR>(
+ "PowerMode::sendIpsData: SET_CFG_DATA[IPS] FAILED!");
+ }
+ }
+
+ return status;
+}
+
+inline void OccPersistData::print()
+{
+ log<level::DEBUG>(
+ fmt::format(
+ "OccPersistData: OEM Mode: 0x{:02X}, OEM Mode Freq: {} (0x{:04X})",
+ oemData.oemMode, oemData.oemModeFreq, oemData.oemModeFreq)
+ .c_str());
+}
+
+// Saves the OEM mode data in the filesystem using cereal.
+void OccPersistData::save()
+{
+ std::filesystem::path opath =
+ std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / oemModeFilename;
+
+ if (!std::filesystem::exists(opath.parent_path()))
+ {
+ std::filesystem::create_directory(opath.parent_path());
+ }
+
+ log<level::DEBUG>(
+ fmt::format("OccPersistData::save: Writing OEM persisted data to {}",
+ opath.c_str())
+ .c_str());
+ print();
+
+ std::ofstream stream{opath.c_str()};
+ cereal::JSONOutputArchive oarchive{stream};
+
+ oarchive(oemData);
+}
+
+// Loads the OEM mode data in the filesystem using cereal.
+void OccPersistData::load()
+{
+
+ std::filesystem::path ipath =
+ std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / oemModeFilename;
+
+ if (!std::filesystem::exists(ipath))
+ {
+ return;
+ }
+
+ log<level::DEBUG>(
+ fmt::format("OccPersistData::load: Reading OEM persisted data from {}",
+ ipath.c_str())
+ .c_str());
+ try
+ {
+ std::ifstream stream{ipath.c_str()};
+ cereal::JSONInputArchive iarchive(stream);
+ iarchive(oemData);
+
+ oemSet = true;
+ }
+ catch (const std::exception& e)
+ {
+ auto error = errno;
+ log<level::ERR>(
+ fmt::format("OccPersistData::load: failed to read {}, errno={}",
+ ipath.c_str(), error)
+ .c_str());
+ }
+
+ print();
+}
+
+void OccPersistData::purge()
+{
+ std::filesystem::path opath =
+ std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / oemModeFilename;
+
+ if (!std::filesystem::exists(opath))
+ {
+ return;
+ }
+
+ print();
+ log<level::DEBUG>("OccPersistData::purge() Removing OEM data");
+
+ oemSet = false;
+ oemData.oemMode = SysPwrMode::NO_CHANGE;
+ oemData.oemModeFreq = 0x0000;
+ remove(opath.c_str());
+}
+
} // namespace powermode
} // namespace occ