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
updatePcapBounds()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)
getOccInput(uint32_t pcap,bool pcapEnabled)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
getPcap()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
getPcapEnabled()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
getPcapFilename(const std::regex & expr)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
writeOcc(uint32_t pcapValue)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
readUserCapHwmon()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
pcapChanged(sdbusplus::message_t & msg)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
updateDbusPcapLimits(uint32_t softMin,uint32_t hardMin,uint32_t max)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
readDbusPcapLimits(uint32_t & softMin,uint32_t & hardMin,uint32_t & max)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