#include "occ_status.hpp" #include #include #include #include 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(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(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(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> msgData; msg.read(msgSensor, msgData); bool changeFound = false; for (const auto& [prop, value] : msgData) { if (prop == POWER_CAP_PROP) { pcap = std::get(value); pcapEnabled = getPcapEnabled(); changeFound = true; } else if (prop == POWER_CAP_ENABLE_PROP) { pcapEnabled = std::get(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(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(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(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(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