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