1 #include "occ_status.hpp" 2 3 #include <fmt/core.h> 4 5 #include <phosphor-logging/log.hpp> 6 #include <powercap.hpp> 7 8 #include <cassert> 9 #include <filesystem> 10 11 namespace open_power 12 { 13 namespace occ 14 { 15 namespace powercap 16 { 17 18 constexpr auto PCAP_PATH = "/xyz/openbmc_project/control/host0/power_cap"; 19 constexpr auto PCAP_INTERFACE = "xyz.openbmc_project.Control.Power.Cap"; 20 21 constexpr auto POWER_CAP_PROP = "PowerCap"; 22 constexpr auto POWER_CAP_ENABLE_PROP = "PowerCapEnable"; 23 constexpr auto POWER_CAP_SOFT_MIN = "MinSoftPowerCapValue"; 24 constexpr auto POWER_CAP_HARD_MIN = "MinPowerCapValue"; 25 constexpr auto POWER_CAP_MAX = "MaxPowerCapValue"; 26 27 using namespace phosphor::logging; 28 namespace fs = std::filesystem; 29 30 void PowerCap::updatePcapBounds() 31 { 32 // Build the hwmon string to write the power cap bounds 33 fs::path minName = getPcapFilename(std::regex{"power\\d+_cap_min$"}); 34 fs::path softMinName = 35 getPcapFilename(std::regex{"power\\d+_cap_min_soft$"}); 36 fs::path maxName = getPcapFilename(std::regex{"power\\d+_cap_max$"}); 37 38 // Read the current cap bounds from dbus 39 uint32_t capSoftMin, capHardMin, capMax; 40 readDbusPcapLimits(capSoftMin, capHardMin, capMax); 41 42 // Read the power cap bounds from sysfs files (from OCC) 43 uint64_t cap; 44 std::ifstream softMinFile(softMinName, std::ios::in); 45 if (softMinFile) 46 { 47 softMinFile >> cap; 48 softMinFile.close(); 49 // Convert to input/AC Power in Watts (round up) 50 capSoftMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9); 51 } 52 else 53 { 54 log<level::ERR>( 55 fmt::format( 56 "updatePcapBounds: unable to find pcap_min_soft file: {} (errno={})", 57 pcapBasePathname.c_str(), errno) 58 .c_str()); 59 } 60 61 std::ifstream minFile(minName, std::ios::in); 62 if (minFile) 63 { 64 minFile >> cap; 65 minFile.close(); 66 // Convert to input/AC Power in Watts (round up) 67 capHardMin = ((cap / (PS_DERATING_FACTOR / 100.0) / 1000000) + 0.9); 68 } 69 else 70 { 71 log<level::ERR>( 72 fmt::format( 73 "updatePcapBounds: unable to find cap_min file: {} (errno={})", 74 pcapBasePathname.c_str(), errno) 75 .c_str()); 76 } 77 78 std::ifstream maxFile(maxName, std::ios::in); 79 if (maxFile) 80 { 81 maxFile >> cap; 82 maxFile.close(); 83 // Convert to input/AC Power in Watts (truncate remainder) 84 capMax = cap / (PS_DERATING_FACTOR / 100.0) / 1000000; 85 } 86 else 87 { 88 log<level::ERR>( 89 fmt::format( 90 "updatePcapBounds: unable to find cap_max file: {} (errno={})", 91 pcapBasePathname.c_str(), errno) 92 .c_str()); 93 } 94 95 // Save the power cap bounds to dbus 96 updateDbusPcapLimits(capSoftMin, capHardMin, capMax); 97 98 // Validate user power cap (if enabled) is within the bounds 99 const uint32_t dbusUserCap = getPcap(); 100 const bool pcapEnabled = getPcapEnabled(); 101 if (pcapEnabled && (dbusUserCap != 0)) 102 { 103 const uint32_t hwmonUserCap = readUserCapHwmon(); 104 if ((dbusUserCap >= capSoftMin) && (dbusUserCap <= capMax)) 105 { 106 // Validate dbus and hwmon user caps match 107 if ((hwmonUserCap != 0) && (dbusUserCap != hwmonUserCap)) 108 { 109 // User power cap is enabled, but does not match dbus 110 log<level::ERR>( 111 fmt::format( 112 "updatePcapBounds: user powercap mismatch (hwmon:{}W, bdus:{}W) - using dbus", 113 hwmonUserCap, dbusUserCap) 114 .c_str()); 115 auto occInput = getOccInput(dbusUserCap, pcapEnabled); 116 writeOcc(occInput); 117 } 118 } 119 else 120 { 121 // User power cap is outside of current bounds 122 uint32_t newCap = capMax; 123 if (dbusUserCap < capSoftMin) 124 { 125 newCap = capSoftMin; 126 } 127 log<level::ERR>( 128 fmt::format( 129 "updatePcapBounds: user powercap {}W is outside bounds " 130 "(soft min:{}, min:{}, max:{})", 131 dbusUserCap, capSoftMin, capHardMin, capMax) 132 .c_str()); 133 try 134 { 135 log<level::INFO>( 136 fmt::format( 137 "updatePcapBounds: Updating user powercap from {} to {}W", 138 hwmonUserCap, newCap) 139 .c_str()); 140 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, 141 newCap); 142 auto occInput = getOccInput(newCap, pcapEnabled); 143 writeOcc(occInput); 144 } 145 catch (const sdbusplus::exception_t& e) 146 { 147 log<level::ERR>( 148 fmt::format( 149 "updatePcapBounds: Failed to update user powercap due to ", 150 e.what()) 151 .c_str()); 152 } 153 } 154 } 155 } 156 157 // Get value of power cap to send to the OCC (output/DC power) 158 uint32_t PowerCap::getOccInput(uint32_t pcap, bool pcapEnabled) 159 { 160 if (!pcapEnabled) 161 { 162 // Pcap disabled, return 0 to indicate disabled 163 return 0; 164 } 165 166 // If pcap is not disabled then just return the pcap with the derating 167 // factor applied (output/DC power). 168 return ((static_cast<uint64_t>(pcap) * PS_DERATING_FACTOR) / 100); 169 } 170 171 uint32_t PowerCap::getPcap() 172 { 173 utils::PropertyValue pcap{}; 174 try 175 { 176 pcap = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP); 177 178 return std::get<uint32_t>(pcap); 179 } 180 catch (const sdbusplus::exception_t& e) 181 { 182 log<level::ERR>("Failed to get PowerCap property", 183 entry("ERROR=%s", e.what()), 184 entry("PATH=%s", PCAP_PATH)); 185 186 return 0; 187 } 188 } 189 190 bool PowerCap::getPcapEnabled() 191 { 192 utils::PropertyValue pcapEnabled{}; 193 try 194 { 195 pcapEnabled = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, 196 POWER_CAP_ENABLE_PROP); 197 198 return std::get<bool>(pcapEnabled); 199 } 200 catch (const sdbusplus::exception_t& e) 201 { 202 log<level::ERR>("Failed to get PowerCapEnable property", 203 entry("ERROR=%s", e.what()), 204 entry("PATH=%s", PCAP_PATH)); 205 206 return false; 207 } 208 } 209 210 fs::path PowerCap::getPcapFilename(const std::regex& expr) 211 { 212 if (pcapBasePathname.empty()) 213 { 214 pcapBasePathname = occStatus.getHwmonPath(); 215 } 216 217 if (fs::exists(pcapBasePathname)) 218 { 219 // Search for pcap file based on the supplied expr 220 for (auto& file : fs::directory_iterator(pcapBasePathname)) 221 { 222 if (std::regex_search(file.path().string(), expr)) 223 { 224 // Found match 225 return file; 226 } 227 } 228 } 229 else 230 { 231 log<level::ERR>(fmt::format("Power Cap base filename not found: {}", 232 pcapBasePathname.c_str()) 233 .c_str()); 234 } 235 236 // return empty path 237 return fs::path{}; 238 } 239 240 // Write the user power cap to sysfs (output/DC power) 241 // This will trigger the driver to send the cap to the OCC 242 void PowerCap::writeOcc(uint32_t pcapValue) 243 { 244 if (!occStatus.occActive()) 245 { 246 // OCC not running, skip update 247 return; 248 } 249 250 // Build the hwmon string to write the user power cap 251 fs::path fileName = getPcapFilename(std::regex{"power\\d+_cap_user$"}); 252 if (fileName.empty()) 253 { 254 log<level::ERR>( 255 fmt::format("Could not find a power cap file to write to: {})", 256 pcapBasePathname.c_str()) 257 .c_str()); 258 return; 259 } 260 261 uint64_t microWatts = pcapValue * 1000000ull; 262 263 auto pcapString{std::to_string(microWatts)}; 264 265 // Open the hwmon file and write the power cap 266 std::ofstream file(fileName, std::ios::out); 267 if (file) 268 { 269 log<level::INFO>(fmt::format("Writing {}uW to {}", pcapString.c_str(), 270 fileName.c_str()) 271 .c_str()); 272 file << pcapString; 273 file.close(); 274 } 275 else 276 { 277 log<level::ERR>(fmt::format("Failed writing {}uW to {} (errno={})", 278 microWatts, fileName.c_str(), errno) 279 .c_str()); 280 } 281 282 return; 283 } 284 285 // Read the current user power cap from sysfs as input/AC power 286 uint32_t PowerCap::readUserCapHwmon() 287 { 288 uint32_t userCap = 0; 289 290 // Get the sysfs filename for the user power cap 291 fs::path userCapName = getPcapFilename(std::regex{"power\\d+_cap_user$"}); 292 if (userCapName.empty()) 293 { 294 log<level::ERR>( 295 fmt::format( 296 "readUserCapHwmon: Could not find a power cap file to read: {})", 297 pcapBasePathname.c_str()) 298 .c_str()); 299 return 0; 300 } 301 302 // Open the sysfs file and read the power cap 303 std::ifstream file(userCapName, std::ios::in); 304 if (file) 305 { 306 uint64_t cap; 307 file >> cap; 308 file.close(); 309 // Convert to input/AC Power in Watts 310 userCap = (cap / (PS_DERATING_FACTOR / 100.0) / 1000000); 311 } 312 else 313 { 314 log<level::ERR>( 315 fmt::format("readUserCapHwmon: Failed reading {} (errno={})", 316 userCapName.c_str(), errno) 317 .c_str()); 318 } 319 320 return userCap; 321 } 322 323 void PowerCap::pcapChanged(sdbusplus::message_t& msg) 324 { 325 if (!occStatus.occActive()) 326 { 327 // Nothing to do 328 return; 329 } 330 331 uint32_t pcap = 0; 332 bool pcapEnabled = false; 333 334 std::string msgSensor; 335 std::map<std::string, std::variant<uint32_t, bool>> msgData; 336 msg.read(msgSensor, msgData); 337 338 bool changeFound = false; 339 for (const auto& [prop, value] : msgData) 340 { 341 if (prop == POWER_CAP_PROP) 342 { 343 pcap = std::get<uint32_t>(value); 344 pcapEnabled = getPcapEnabled(); 345 changeFound = true; 346 } 347 else if (prop == POWER_CAP_ENABLE_PROP) 348 { 349 pcapEnabled = std::get<bool>(value); 350 pcap = getPcap(); 351 changeFound = true; 352 } 353 else 354 { 355 // Ignore other properties 356 log<level::DEBUG>( 357 fmt::format( 358 "pcapChanged: Unknown power cap property changed {} to {}", 359 prop.c_str(), std::get<uint32_t>(value)) 360 .c_str()); 361 } 362 } 363 364 // Validate the cap is within supported range 365 uint32_t capSoftMin, capHardMin, capMax; 366 readDbusPcapLimits(capSoftMin, capHardMin, capMax); 367 if (((pcap > 0) && (pcap < capSoftMin)) || ((pcap == 0) && (pcapEnabled))) 368 { 369 log<level::ERR>( 370 fmt::format( 371 "pcapChanged: Power cap of {}W is lower than allowed (soft min:{}, min:{}) - using soft min", 372 pcap, capSoftMin, capHardMin) 373 .c_str()); 374 pcap = capSoftMin; 375 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap); 376 } 377 else if (pcap > capMax) 378 { 379 log<level::ERR>( 380 fmt::format( 381 "pcapChanged: Power cap of {}W is higher than allowed (max:{}) - using max", 382 pcap, capSoftMin, capHardMin) 383 .c_str()); 384 pcap = capMax; 385 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_PROP, pcap); 386 } 387 388 if (changeFound) 389 { 390 log<level::INFO>( 391 fmt::format( 392 "Power Cap Property Change (cap={}W (input), enabled={})", pcap, 393 pcapEnabled ? 'y' : 'n') 394 .c_str()); 395 396 // Determine desired action to write to occ 397 auto occInput = getOccInput(pcap, pcapEnabled); 398 // Write action to occ 399 writeOcc(occInput); 400 } 401 402 return; 403 } 404 405 // Update the Power Cap bounds on DBus 406 bool PowerCap::updateDbusPcapLimits(uint32_t softMin, uint32_t hardMin, 407 uint32_t max) 408 { 409 bool complete = true; 410 411 try 412 { 413 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_SOFT_MIN, 414 softMin); 415 } 416 catch (const sdbusplus::exception_t& e) 417 { 418 log<level::ERR>( 419 fmt::format( 420 "updateDbusPcapLimits: Failed to set SOFT PCAP to {}W due to {}", 421 softMin, e.what()) 422 .c_str()); 423 complete = false; 424 } 425 426 try 427 { 428 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_HARD_MIN, 429 hardMin); 430 } 431 catch (const sdbusplus::exception_t& e) 432 { 433 log<level::ERR>( 434 fmt::format( 435 "updateDbusPcapLimits: Failed to set HARD PCAP to {}W due to {}", 436 hardMin, e.what()) 437 .c_str()); 438 complete = false; 439 } 440 441 try 442 { 443 utils::setProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX, max); 444 } 445 catch (const sdbusplus::exception_t& e) 446 { 447 log<level::ERR>( 448 fmt::format( 449 "updateDbusPcapLimits: Failed to set MAX PCAP to {}W due to {}", 450 max, e.what()) 451 .c_str()); 452 complete = false; 453 } 454 455 return complete; 456 } 457 458 // Read the Power Cap bounds from DBus 459 bool PowerCap::readDbusPcapLimits(uint32_t& softMin, uint32_t& hardMin, 460 uint32_t& max) 461 { 462 bool complete = true; 463 utils::PropertyValue prop{}; 464 465 try 466 { 467 prop = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, 468 POWER_CAP_SOFT_MIN); 469 softMin = std::get<uint32_t>(prop); 470 } 471 catch (const sdbusplus::exception_t& e) 472 { 473 log<level::ERR>( 474 fmt::format("readDbusPcapLimits: Failed to get SOFT PCAP due to {}", 475 e.what()) 476 .c_str()); 477 softMin = 0; 478 complete = false; 479 } 480 481 try 482 { 483 prop = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, 484 POWER_CAP_HARD_MIN); 485 hardMin = std::get<uint32_t>(prop); 486 } 487 catch (const sdbusplus::exception_t& e) 488 { 489 log<level::ERR>( 490 fmt::format("readDbusPcapLimits: Failed to get HARD PCAP due to {}", 491 e.what()) 492 .c_str()); 493 hardMin = 0; 494 complete = false; 495 } 496 497 try 498 { 499 prop = utils::getProperty(PCAP_PATH, PCAP_INTERFACE, POWER_CAP_MAX); 500 max = std::get<uint32_t>(prop); 501 } 502 catch (const sdbusplus::exception_t& e) 503 { 504 log<level::ERR>( 505 fmt::format("readDbusPcapLimits: Failed to get MAX PCAP due to {}", 506 e.what()) 507 .c_str()); 508 max = INT_MAX; 509 complete = false; 510 } 511 512 return complete; 513 } 514 515 } // namespace powercap 516 517 } // namespace occ 518 519 } // namespace open_power 520