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