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