| #include "powermode.hpp" |
| |
| #include <fcntl.h> |
| #include <fmt/core.h> |
| #include <sys/ioctl.h> |
| |
| #ifdef POWERVM_CHECK |
| #include <com/ibm/Host/Target/server.hpp> |
| #endif |
| #include <org/open_power/OCC/Device/error.hpp> |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| #include <xyz/openbmc_project/Control/Power/Mode/server.hpp> |
| |
| #include <cassert> |
| #include <fstream> |
| #include <regex> |
| |
| namespace open_power |
| { |
| namespace occ |
| { |
| namespace powermode |
| { |
| |
| using namespace phosphor::logging; |
| using namespace std::literals::string_literals; |
| using namespace sdbusplus::org::open_power::OCC::Device::Error; |
| |
| using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode; |
| |
| using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed; |
| using Reason = xyz::openbmc_project::Common::NotAllowed::REASON; |
| |
| // Set the Master OCC |
| void PowerMode::setMasterOcc(const std::string& masterOccPath) |
| { |
| if (masterOccSet) |
| { |
| if (masterOccPath != path) |
| { |
| log<level::ERR>( |
| fmt::format( |
| "PowerMode::setMasterOcc: Master changed (was OCC{}, {})", |
| occInstance, masterOccPath) |
| .c_str()); |
| if (occCmd) |
| { |
| occCmd.reset(); |
| } |
| } |
| } |
| path = masterOccPath; |
| occInstance = path.back() - '0'; |
| log<level::DEBUG>(fmt::format("PowerMode::setMasterOcc(OCC{}, {})", |
| occInstance, path.c_str()) |
| .c_str()); |
| if (!occCmd) |
| { |
| occCmd = std::make_unique<open_power::occ::OccCommand>(occInstance, |
| path.c_str()); |
| } |
| masterOccSet = true; |
| }; |
| |
| // Called when DBus power mode gets changed |
| void PowerMode::modeChanged(sdbusplus::message_t& msg) |
| { |
| std::map<std::string, std::variant<std::string>> properties{}; |
| std::string interface; |
| std::string propVal; |
| msg.read(interface, properties); |
| const auto modeEntry = properties.find(POWER_MODE_PROP); |
| if (modeEntry != properties.end()) |
| { |
| auto modeEntryValue = modeEntry->second; |
| propVal = std::get<std::string>(modeEntryValue); |
| SysPwrMode newMode = convertStringToMode(propVal); |
| |
| // If mode set was requested via direct dbus property change then we |
| // need to see if mode set is locked and ignore the request. We should |
| // not get to this path since the property change method should also be |
| // checking the mode lock status. |
| if (persistedData.getModeLock()) |
| { |
| // Fix up the mode property since we are going to ignore this |
| // set mode request. |
| log<level::ERR>( |
| "PowerMode::modeChanged: mode property changed while locked"); |
| SysPwrMode currentMode; |
| uint16_t oemModeData; |
| getMode(currentMode, oemModeData); |
| updateDbusMode(currentMode); |
| return; |
| } |
| |
| if (newMode != SysPwrMode::NO_CHANGE) |
| { |
| // Update persisted data with new mode |
| persistedData.updateMode(newMode, 0); |
| |
| log<level::INFO>( |
| fmt::format("DBus Power Mode Changed: {}", propVal).c_str()); |
| |
| // Send mode change to OCC |
| sendModeChange(); |
| } |
| } |
| } |
| |
| // Set the state of power mode lock. Writing persistent data via dbus method. |
| bool PowerMode::powerModeLock() |
| { |
| log<level::INFO>("PowerMode::powerModeLock: locking mode change"); |
| persistedData.updateModeLock(true); // write persistent data |
| return true; |
| } |
| |
| // Get the state of power mode. Reading persistent data via dbus method. |
| bool PowerMode::powerModeLockStatus() |
| { |
| bool status = persistedData.getModeLock(); // read persistent data |
| log<level::INFO>(fmt::format("PowerMode::powerModeLockStatus: {}", |
| status ? "locked" : "unlocked") |
| .c_str()); |
| return status; |
| } |
| |
| // Called from OCC PassThrough interface (via CE login / BMC command line) |
| bool PowerMode::setMode(const SysPwrMode newMode, const uint16_t oemModeData) |
| { |
| if (persistedData.getModeLock()) |
| { |
| log<level::INFO>("PowerMode::setMode: mode change blocked"); |
| return false; |
| } |
| |
| if (updateDbusMode(newMode) == false) |
| { |
| // Unsupported mode |
| return false; |
| } |
| |
| // Save mode |
| persistedData.updateMode(newMode, oemModeData); |
| |
| // Send mode change to OCC |
| if (sendModeChange() != CmdStatus::SUCCESS) |
| { |
| // Mode change failed |
| return false; |
| } |
| |
| 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; |
| |
| Mode::PowerMode mode = Mode::convertPowerModeFromString(i_modeString); |
| if (mode == Mode::PowerMode::MaximumPerformance) |
| { |
| pmode = SysPwrMode::MAX_PERF; |
| } |
| else if (mode == Mode::PowerMode::PowerSaving) |
| { |
| pmode = SysPwrMode::POWER_SAVING; |
| } |
| else if (mode == Mode::PowerMode::Static) |
| { |
| pmode = SysPwrMode::STATIC; |
| } |
| else |
| { |
| if (mode != Mode::PowerMode::OEM) |
| { |
| log<level::ERR>( |
| fmt::format( |
| "convertStringToMode: Invalid Power Mode specified: {}", |
| i_modeString) |
| .c_str()); |
| } |
| } |
| |
| return pmode; |
| } |
| |
| // Check if Hypervisor target is PowerVM |
| bool isPowerVM() |
| { |
| bool powerVmTarget = true; |
| #ifdef POWERVM_CHECK |
| 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"; |
| |
| // 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()); |
| #endif |
| |
| return powerVmTarget; |
| } |
| |
| // Initialize persistent data and return true if successful |
| bool PowerMode::initPersistentData() |
| { |
| if (!persistedData.modeAvailable()) |
| { |
| // Read the default mode |
| SysPwrMode currentMode; |
| if (!getDefaultMode(currentMode)) |
| { |
| // Unable to read defaults |
| return false; |
| } |
| log<level::INFO>( |
| fmt::format("PowerMode::initPersistentData: Using default mode: {}", |
| currentMode) |
| .c_str()); |
| |
| // Save default mode as current mode |
| persistedData.updateMode(currentMode, 0); |
| |
| // Write default mode to DBus |
| updateDbusMode(currentMode); |
| } |
| |
| if (!persistedData.ipsAvailable()) |
| { |
| // Read the default IPS parameters, write persistent file and update |
| // DBus |
| return useDefaultIPSParms(); |
| } |
| return true; |
| } |
| |
| // Get the requested power mode and return true if successful |
| bool PowerMode::getMode(SysPwrMode& currentMode, uint16_t& oemModeData) |
| { |
| currentMode = SysPwrMode::NO_CHANGE; |
| oemModeData = 0; |
| |
| if (!persistedData.getMode(currentMode, oemModeData)) |
| { |
| // Persistent data not initialized, read defaults and update DBus |
| if (!initPersistentData()) |
| { |
| // Unable to read defaults from entity manager yet |
| return false; |
| } |
| return persistedData.getMode(currentMode, oemModeData); |
| } |
| |
| return true; |
| } |
| |
| // Set the power mode on DBus |
| bool PowerMode::updateDbusMode(const SysPwrMode newMode) |
| { |
| 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; |
| } |
| |
| // Convert mode for DBus |
| ModeInterface::PowerMode dBusMode; |
| switch (newMode) |
| { |
| case SysPwrMode::STATIC: |
| dBusMode = Mode::PowerMode::Static; |
| break; |
| case SysPwrMode::POWER_SAVING: |
| dBusMode = Mode::PowerMode::PowerSaving; |
| break; |
| case SysPwrMode::MAX_PERF: |
| dBusMode = Mode::PowerMode::MaximumPerformance; |
| break; |
| default: |
| dBusMode = Mode::PowerMode::OEM; |
| } |
| |
| // true = skip update signal |
| ModeInterface::setPropertyByName(POWER_MODE_PROP, dBusMode, true); |
| |
| return true; |
| } |
| |
| // Send mode change request to the master OCC |
| CmdStatus PowerMode::sendModeChange() |
| { |
| CmdStatus status; |
| |
| if (!masterActive || !masterOccSet) |
| { |
| // Nothing to do |
| log<level::DEBUG>("PowerMode::sendModeChange: OCC master not active"); |
| 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; |
| } |
| |
| SysPwrMode newMode; |
| uint16_t oemModeData = 0; |
| getMode(newMode, oemModeData); |
| |
| 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(oemModeData >> 8); // Mode Data (Freq Point) |
| cmd.push_back(oemModeData & 0xFF); // |
| cmd.push_back(0x00); // reserved |
| log<level::INFO>( |
| fmt::format( |
| "PowerMode::sendModeChange: SET_MODE({},{}) command to OCC{} ({} bytes)", |
| newMode, oemModeData, 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 |
| { |
| log<level::ERR>( |
| fmt::format( |
| "PowerMode::sendModeChange: SET_MODE FAILED with status={}", |
| status) |
| .c_str()); |
| } |
| } |
| 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_t& msg) |
| { |
| bool parmsChanged = false; |
| std::string interface; |
| std::map<std::string, std::variant<bool, uint8_t, uint64_t>> |
| ipsProperties{}; |
| msg.read(interface, ipsProperties); |
| |
| // Read persisted values |
| bool ipsEnabled; |
| uint8_t enterUtil, exitUtil; |
| uint16_t enterTime, exitTime; |
| getIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime); |
| |
| // Check for any changed data |
| auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| ipsEnabled = std::get<bool>(ipsEntry->second); |
| log<level::INFO>( |
| fmt::format("Idle Power Saver change: Enabled={}", ipsEnabled) |
| .c_str()); |
| parmsChanged = true; |
| } |
| ipsEntry = ipsProperties.find(IPS_ENTER_UTIL); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| enterUtil = std::get<uint8_t>(ipsEntry->second); |
| log<level::INFO>( |
| fmt::format("Idle Power Saver change: Enter Util={}%", enterUtil) |
| .c_str()); |
| parmsChanged = true; |
| } |
| 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(); |
| log<level::INFO>( |
| fmt::format("Idle Power Saver change: Enter Time={}sec", enterTime) |
| .c_str()); |
| parmsChanged = true; |
| } |
| ipsEntry = ipsProperties.find(IPS_EXIT_UTIL); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| exitUtil = std::get<uint8_t>(ipsEntry->second); |
| log<level::INFO>( |
| fmt::format("Idle Power Saver change: Exit Util={}%", exitUtil) |
| .c_str()); |
| parmsChanged = true; |
| } |
| 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(); |
| log<level::INFO>( |
| fmt::format("Idle Power Saver change: Exit Time={}sec", exitTime) |
| .c_str()); |
| parmsChanged = true; |
| } |
| |
| if (parmsChanged) |
| { |
| if (exitUtil == 0) |
| { |
| // Setting the exitUtil to 0 will force restoring the default IPS |
| // parmeters (0 is not valid exit utilization) |
| log<level::INFO>( |
| "Idle Power Saver Exit Utilization is 0%. Restoring default parameters"); |
| // Read the default IPS parameters, write persistent file and update |
| // DBus |
| useDefaultIPSParms(); |
| } |
| else |
| { |
| // Update persistant data with new DBus values |
| persistedData.updateIPS(ipsEnabled, enterUtil, enterTime, exitUtil, |
| exitTime); |
| } |
| |
| // Trigger IPS data to get sent to the OCC |
| sendIpsData(); |
| } |
| |
| return; |
| } |
| |
| /** @brief Get the Idle Power Saver properties from persisted data |
| * @return true if IPS parameters were read |
| */ |
| bool PowerMode::getIPSParms(bool& ipsEnabled, uint8_t& enterUtil, |
| uint16_t& enterTime, uint8_t& exitUtil, |
| uint16_t& exitTime) |
| { |
| // Defaults: |
| ipsEnabled = true; // Enabled |
| enterUtil = 8; // Enter Utilization (8%) |
| enterTime = 240; // Enter Delay Time (240s) |
| exitUtil = 12; // Exit Utilization (12%) |
| exitTime = 10; // Exit Delay Time (10s) |
| |
| if (!persistedData.getIPS(ipsEnabled, enterUtil, enterTime, exitUtil, |
| exitTime)) |
| { |
| // Persistent data not initialized, read defaults and update DBus |
| if (!initPersistentData()) |
| { |
| // Unable to read defaults from entity manager yet |
| return false; |
| } |
| |
| persistedData.getIPS(ipsEnabled, enterUtil, enterTime, exitUtil, |
| exitTime); |
| } |
| |
| 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 true; |
| } |
| |
| // Set the Idle Power Saver data on DBus |
| bool PowerMode::updateDbusIPS(const bool enabled, const uint8_t enterUtil, |
| const uint16_t enterTime, const uint8_t exitUtil, |
| const uint16_t exitTime) |
| { |
| // true = skip update signal |
| IpsInterface::setPropertyByName(IPS_ENABLED_PROP, enabled, true); |
| IpsInterface::setPropertyByName(IPS_ENTER_UTIL, enterUtil, true); |
| // Convert time from seconds to ms |
| uint64_t msTime = enterTime * 1000; |
| IpsInterface::setPropertyByName(IPS_ENTER_TIME, msTime, true); |
| IpsInterface::setPropertyByName(IPS_EXIT_UTIL, exitUtil, true); |
| msTime = exitTime * 1000; |
| IpsInterface::setPropertyByName(IPS_EXIT_TIME, msTime, true); |
| |
| return true; |
| } |
| |
| // Send Idle Power Saver config data to the master OCC |
| CmdStatus PowerMode::sendIpsData() |
| { |
| if (!masterActive || !masterOccSet) |
| { |
| // 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; |
| } |
| |
| bool ipsEnabled; |
| uint8_t enterUtil, exitUtil; |
| uint16_t enterTime, exitTime; |
| getIPSParms(ipsEnabled, 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()); |
| CmdStatus 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 |
| { |
| log<level::ERR>( |
| fmt::format( |
| "PowerMode::sendIpsData: SET_CFG_DATA[IPS] with status={}", |
| status) |
| .c_str()); |
| } |
| |
| return status; |
| } |
| |
| // Print the current values |
| void OccPersistData::print() |
| { |
| if (modeData.modeInitialized) |
| { |
| log<level::INFO>( |
| fmt::format( |
| "OccPersistData: Mode: 0x{:02X}, OEM Mode Data: {} (0x{:04X} Locked{})", |
| modeData.mode, modeData.oemModeData, modeData.oemModeData, |
| modeData.modeLocked) |
| .c_str()); |
| } |
| if (modeData.ipsInitialized) |
| { |
| log<level::INFO>( |
| fmt::format( |
| "OccPersistData: IPS enabled:{}, enter:{}%/{}s, exit:{}%/{}s", |
| modeData.ipsEnabled, modeData.ipsEnterUtil, |
| modeData.ipsEnterTime, modeData.ipsExitUtil, |
| modeData.ipsExitTime) |
| .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} / powerModeFilename; |
| |
| if (!std::filesystem::exists(opath.parent_path())) |
| { |
| std::filesystem::create_directory(opath.parent_path()); |
| } |
| |
| log<level::DEBUG>( |
| fmt::format( |
| "OccPersistData::save: Writing Power Mode persisted data to {}", |
| opath.c_str()) |
| .c_str()); |
| // print(); |
| |
| std::ofstream stream{opath.c_str()}; |
| cereal::JSONOutputArchive oarchive{stream}; |
| |
| oarchive(modeData); |
| } |
| |
| // Loads the OEM mode data in the filesystem using cereal. |
| void OccPersistData::load() |
| { |
| |
| std::filesystem::path ipath = |
| std::filesystem::path{OCC_CONTROL_PERSIST_PATH} / powerModeFilename; |
| |
| if (!std::filesystem::exists(ipath)) |
| { |
| modeData.modeInitialized = false; |
| modeData.ipsInitialized = false; |
| return; |
| } |
| |
| log<level::DEBUG>( |
| fmt::format( |
| "OccPersistData::load: Reading Power Mode persisted data from {}", |
| ipath.c_str()) |
| .c_str()); |
| try |
| { |
| std::ifstream stream{ipath.c_str()}; |
| cereal::JSONInputArchive iarchive(stream); |
| iarchive(modeData); |
| } |
| 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()); |
| modeData.modeInitialized = false; |
| modeData.ipsInitialized = false; |
| } |
| |
| // print(); |
| } |
| |
| // Called when PowerModeProperties defaults are available on DBus |
| void PowerMode::defaultsReady(sdbusplus::message_t& msg) |
| { |
| std::map<std::string, std::variant<std::string>> properties{}; |
| std::string interface; |
| msg.read(interface, properties); |
| |
| // If persistent data exists, then don't need to read defaults |
| if ((!persistedData.modeAvailable()) || (!persistedData.ipsAvailable())) |
| { |
| log<level::INFO>( |
| fmt::format( |
| "Default PowerModeProperties are now available (persistent modeAvail={}, ipsAvail={})", |
| persistedData.modeAvailable() ? 'y' : 'n', |
| persistedData.modeAvailable() ? 'y' : 'n') |
| .c_str()); |
| |
| // Read default power mode defaults and update DBus |
| initPersistentData(); |
| } |
| } |
| |
| // Get the default power mode from DBus and return true if success |
| bool PowerMode::getDefaultMode(SysPwrMode& defaultMode) |
| { |
| try |
| { |
| auto& bus = utils::getBus(); |
| std::string path = "/"; |
| std::string service = |
| utils::getServiceUsingSubTree(PMODE_DEFAULT_INTERFACE, path); |
| auto method = |
| bus.new_method_call(service.c_str(), path.c_str(), |
| "org.freedesktop.DBus.Properties", "Get"); |
| method.append(PMODE_DEFAULT_INTERFACE, "PowerMode"); |
| auto reply = bus.call(method); |
| |
| std::variant<std::string> stateEntryValue; |
| reply.read(stateEntryValue); |
| auto propVal = std::get<std::string>(stateEntryValue); |
| |
| const std::string fullModeString = |
| PMODE_INTERFACE + ".PowerMode."s + propVal; |
| defaultMode = powermode::convertStringToMode(fullModeString); |
| if (!VALID_POWER_MODE_SETTING(defaultMode)) |
| { |
| log<level::ERR>( |
| fmt::format( |
| "PowerMode::getDefaultMode: Invalid default power mode found: {}", |
| defaultMode) |
| .c_str()); |
| // If default was read but not valid, use Max Performance |
| defaultMode = SysPwrMode::MAX_PERF; |
| return true; |
| } |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| log<level::ERR>( |
| fmt::format("Unable to read Default Power Mode: {}", e.what()) |
| .c_str()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* Get the default Idle Power Saver properties and return true if successful */ |
| bool PowerMode::getDefaultIPSParms(bool& ipsEnabled, uint8_t& enterUtil, |
| uint16_t& enterTime, uint8_t& exitUtil, |
| uint16_t& exitTime) |
| { |
| // Defaults: |
| ipsEnabled = true; // Enabled |
| 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, uint16_t, uint64_t>> |
| ipsProperties{}; |
| |
| // Get all IPS properties from DBus |
| try |
| { |
| auto& bus = utils::getBus(); |
| std::string path = "/"; |
| std::string service = |
| utils::getServiceUsingSubTree(PMODE_DEFAULT_INTERFACE, path); |
| auto method = |
| bus.new_method_call(service.c_str(), path.c_str(), |
| "org.freedesktop.DBus.Properties", "GetAll"); |
| method.append(PMODE_DEFAULT_INTERFACE); |
| auto reply = bus.call(method); |
| reply.read(ipsProperties); |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| log<level::ERR>( |
| fmt::format( |
| "Unable to read Default Idle Power Saver parameters so it will be disabled: {}", |
| e.what()) |
| .c_str()); |
| return false; |
| } |
| |
| auto ipsEntry = ipsProperties.find("IdlePowerSaverEnabled"); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| ipsEnabled = std::get<bool>(ipsEntry->second); |
| } |
| else |
| { |
| log<level::ERR>( |
| "PowerMode::getDefaultIPSParms could not find property: IdlePowerSaverEnabled"); |
| } |
| |
| ipsEntry = ipsProperties.find("EnterUtilizationPercent"); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| enterUtil = std::get<uint64_t>(ipsEntry->second); |
| } |
| else |
| { |
| log<level::ERR>( |
| "PowerMode::getDefaultIPSParms could not find property: EnterUtilizationPercent"); |
| } |
| |
| ipsEntry = ipsProperties.find("EnterUtilizationDwellTime"); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| enterTime = std::get<uint64_t>(ipsEntry->second); |
| } |
| else |
| { |
| log<level::ERR>( |
| "PowerMode::getDefaultIPSParms could not find property: EnterUtilizationDwellTime"); |
| } |
| |
| ipsEntry = ipsProperties.find("ExitUtilizationPercent"); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| exitUtil = std::get<uint64_t>(ipsEntry->second); |
| } |
| else |
| { |
| log<level::ERR>( |
| "PowerMode::getDefaultIPSParms could not find property: ExitUtilizationPercent"); |
| } |
| |
| ipsEntry = ipsProperties.find("ExitUtilizationDwellTime"); |
| if (ipsEntry != ipsProperties.end()) |
| { |
| exitTime = std::get<uint64_t>(ipsEntry->second); |
| } |
| else |
| { |
| log<level::ERR>( |
| "PowerMode::getDefaultIPSParms could not find property: ExitUtilizationDwellTime"); |
| } |
| |
| if (enterUtil > exitUtil) |
| { |
| log<level::ERR>( |
| fmt::format( |
| "ERROR: Default Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both", |
| enterUtil, exitUtil) |
| .c_str()); |
| enterUtil = exitUtil; |
| } |
| |
| return true; |
| } |
| |
| /* Read default IPS parameters, save them to the persistent file and update |
| DBus. Return true if successful */ |
| bool PowerMode::useDefaultIPSParms() |
| { |
| // Read the default IPS parameters |
| bool ipsEnabled; |
| uint8_t enterUtil, exitUtil; |
| uint16_t enterTime, exitTime; |
| if (!getDefaultIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, |
| exitTime)) |
| { |
| // Unable to read defaults |
| return false; |
| } |
| log<level::INFO>( |
| fmt::format( |
| "PowerMode::useDefaultIPSParms: Using default IPS parms: Enabled: {}, EnterUtil: {}%, EnterTime: {}s, ExitUtil: {}%, ExitTime: {}s", |
| ipsEnabled, enterUtil, enterTime, exitUtil, exitTime) |
| .c_str()); |
| |
| // Save IPS parms to the persistent file |
| persistedData.updateIPS(ipsEnabled, enterUtil, enterTime, exitUtil, |
| exitTime); |
| |
| // Write IPS parms to DBus |
| return updateDbusIPS(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime); |
| } |
| |
| #ifdef POWER10 |
| |
| // Starts to watch for IPS active state changes. |
| bool PowerMode::openIpsFile() |
| { |
| bool rc = true; |
| fd = open(ipsStatusFile.c_str(), O_RDONLY | O_NONBLOCK); |
| const int open_errno = errno; |
| if (fd < 0) |
| { |
| log<level::ERR>(fmt::format("openIpsFile Error({})={} : File={}", |
| open_errno, strerror(open_errno), |
| ipsStatusFile.c_str()) |
| .c_str()); |
| |
| close(fd); |
| |
| using namespace sdbusplus::org::open_power::OCC::Device::Error; |
| report<OpenFailure>( |
| phosphor::logging::org::open_power::OCC::Device::OpenFailure:: |
| CALLOUT_ERRNO(open_errno), |
| phosphor::logging::org::open_power::OCC::Device::OpenFailure:: |
| CALLOUT_DEVICE_PATH(ipsStatusFile.c_str())); |
| |
| // We are no longer watching the error |
| active(false); |
| |
| watching = false; |
| rc = false; |
| // NOTE: this will leave the system not reporting IPS active state to |
| // Fan Controls, Until an APP reload, or IPL and we will attempt again. |
| } |
| return rc; |
| } |
| |
| // Starts to watch for IPS active state changes. |
| void PowerMode::addIpsWatch(bool poll) |
| { |
| // open file and register callback on file if we are not currently watching, |
| // and if poll=true, and if we are the master. |
| if ((!watching) && poll) |
| { |
| // Open the file |
| if (openIpsFile()) |
| { |
| // register the callback handler which sets 'watching' |
| registerIpsStatusCallBack(); |
| } |
| } |
| } |
| |
| // Stops watching for IPS active state changes. |
| void PowerMode::removeIpsWatch() |
| { |
| // NOTE: we want to remove event, close file, and IPS active false no |
| // matter what the 'watching' flags is set to. |
| |
| // We are no longer watching the error |
| active(false); |
| |
| watching = false; |
| |
| // Close file |
| close(fd); |
| |
| // clears sourcePtr in the event source. |
| eventSource.reset(); |
| } |
| |
| // Attaches the FD to event loop and registers the callback handler |
| void PowerMode::registerIpsStatusCallBack() |
| { |
| decltype(eventSource.get()) sourcePtr = nullptr; |
| |
| auto r = sd_event_add_io(event.get(), &sourcePtr, fd, EPOLLPRI | EPOLLERR, |
| ipsStatusCallBack, this); |
| if (r < 0) |
| { |
| log<level::ERR>(fmt::format("sd_event_add_io: Error({})={} : File={}", |
| r, strerror(-r), ipsStatusFile.c_str()) |
| .c_str()); |
| |
| using InternalFailure = |
| sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; |
| report<InternalFailure>(); |
| |
| removeIpsWatch(); |
| // NOTE: this will leave the system not reporting IPS active state to |
| // Fan Controls, Until an APP reload, or IPL and we will attempt again. |
| } |
| else |
| { |
| // puts sourcePtr in the event source. |
| eventSource.reset(sourcePtr); |
| // Set we are watching the error |
| watching = true; |
| } |
| } |
| |
| // Static function to redirect to non static analyze event function to be |
| // able to read file and push onto dBus. |
| int PowerMode::ipsStatusCallBack(sd_event_source* /*es*/, int /*fd*/, |
| uint32_t /*revents*/, void* userData) |
| { |
| auto pmode = static_cast<PowerMode*>(userData); |
| pmode->analyzeIpsEvent(); |
| return 0; |
| } |
| |
| // Function to Read SysFs file change on IPS state and push on dBus. |
| void PowerMode::analyzeIpsEvent() |
| { |
| // Need to seek to START, else the poll returns immediately telling |
| // there is data to be read. if not done this floods the system. |
| auto r = lseek(fd, 0, SEEK_SET); |
| const int open_errno = errno; |
| if (r < 0) |
| { |
| // NOTE: upon file access error we can not just re-open file, we have to |
| // remove and add to watch. |
| removeIpsWatch(); |
| addIpsWatch(true); |
| } |
| |
| // if we are 'watching' that is the file seek, or the re-open passed.. we |
| // can read the data |
| if (watching) |
| { |
| // This file gets created when polling OCCs. A value or length of 0 is |
| // deemed success. That means we would disable IPS active on dbus. |
| char data; |
| bool ipsState = false; |
| const auto len = read(fd, &data, sizeof(data)); |
| const int readErrno = errno; |
| if (len <= 0) |
| { |
| removeIpsWatch(); |
| |
| log<level::ERR>( |
| fmt::format("IPS state Read Error({})={} : File={} : len={}", |
| readErrno, strerror(readErrno), |
| ipsStatusFile.c_str(), len) |
| .c_str()); |
| |
| report<ReadFailure>( |
| phosphor::logging::org::open_power::OCC::Device::ReadFailure:: |
| CALLOUT_ERRNO(readErrno), |
| phosphor::logging::org::open_power::OCC::Device::ReadFailure:: |
| CALLOUT_DEVICE_PATH(ipsStatusFile.c_str())); |
| |
| // NOTE: this will leave the system not reporting IPS active state |
| // to Fan Controls, Until an APP reload, or IPL and we will attempt |
| // again. |
| } |
| else |
| { |
| // Data returned in ASCII. |
| // convert to integer. atoi() |
| // from OCC_P10_FW_Interfaces spec |
| // Bit 6: IPS active 1 indicates enabled. |
| // Bit 7: IPS enabled. 1 indicates enabled. |
| // mask off bit 6 --> & 0x02 |
| // Shift left one bit and store as bool. >> 1 |
| ipsState = static_cast<bool>(((atoi(&data)) & 0x2) >> 1); |
| } |
| |
| // This will only set IPS active dbus if different than current. |
| active(ipsState); |
| } |
| else |
| { |
| removeIpsWatch(); |
| |
| // If the Retry did not get to "watching = true" we already have an |
| // error log, just post trace. |
| log<level::ERR>(fmt::format("Retry on File seek Error({})={} : File={}", |
| open_errno, strerror(open_errno), |
| ipsStatusFile.c_str()) |
| .c_str()); |
| |
| // NOTE: this will leave the system not reporting IPS active state to |
| // Fan Controls, Until an APP reload, or IPL and we will attempt again. |
| } |
| |
| return; |
| } |
| |
| // overrides read/write to powerMode dbus property. |
| Mode::PowerMode PowerMode::powerMode(Mode::PowerMode value) |
| { |
| if (persistedData.getModeLock()) |
| { |
| log<level::INFO>("PowerMode::powerMode: mode property change blocked"); |
| elog<NotAllowed>(xyz::openbmc_project::Common::NotAllowed::REASON( |
| "mode change not allowed due to lock")); |
| return value; |
| } |
| else |
| { |
| return Mode::powerMode(value); |
| } |
| } |
| #endif |
| |
| /* Set dbus property to SAFE mode(true) or clear(false) only if different */ |
| void PowerMode::updateDbusSafeMode(const bool safeModeReq) |
| { |
| log<level::DEBUG>( |
| fmt::format("PowerMode:updateDbusSafeMode: Update dbus state ({})", |
| safeModeReq) |
| .c_str()); |
| |
| // Note; this function checks and only updates if different. |
| Mode::safeMode(safeModeReq); |
| } |
| |
| } // namespace powermode |
| |
| } // namespace occ |
| |
| } // namespace open_power |