xref: /openbmc/openpower-occ-control/powercap.cpp (revision a49c987eebb63692bb60eed3c4cf0f2f359ba725)
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